././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745694560.0418198 streamlink-7.3.0/0000755000175100001660000000000015003227540013273 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/AUTHORS0000644000175100001660000000033515003227510014341 0ustar00runnerdockerThank you to everyone who has contributed to streamlink / livestreamer. For a list of contributors, see https://github.com/streamlink/streamlink/graphs/contributors or use the command `git log --format='%aN' | sort -uf` ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/CHANGELOG.md0000644000175100001660000036222515003227510015113 0ustar00runnerdocker# Changelog ## streamlink 7.3.0 (2025-04-26) - Changed: download progress to use the console output stream rather than always `stderr`, which previously caused log and progress messages to be interweaved ([#6497](https://github.com/streamlink/streamlink/pull/6497), [#6496](https://github.com/streamlink/streamlink/pull/6496)) - Changed: download progress output to be a status message line at the bottom of the console output, unless `--progress=force` is set in non-interactive or unsupported environments, in which case less frequent regular progress messages are written ([#6497](https://github.com/streamlink/streamlink/pull/6497), [#6496](https://github.com/streamlink/streamlink/pull/6496)) - Fixed: potential division by zero error when formatting progress output ([#6498](https://github.com/streamlink/streamlink/pull/6498)) - Build: bumped `setuptools` requirement from `>=65.6` to `>=77` and switched to PEP 639 project license metadata format ([#6502](https://github.com/streamlink/streamlink/pull/6502)) [Full changelog](https://github.com/streamlink/streamlink/compare/7.2.0...7.3.0) ## streamlink 7.2.0 (2025-04-04) - Added: `decompress` extras marker to Streamlink's optional dependencies for installing `brotli` and `zstandard`, which were previously implied optional dependencies via the transitive dependency `urllib3` ([#6451](https://github.com/streamlink/streamlink/pull/6451)) - Changed: `--retry-streams` to allow a value of `0`, so it can be unset again if set previously ([#6455](https://github.com/streamlink/streamlink/pull/6455)) - Changed: `--quiet` to suppress all text output including errors, not just log output ([#6461](https://github.com/streamlink/streamlink/pull/6461)) - Fixed: `--logfile` affecting the console output stream ([#6461](https://github.com/streamlink/streamlink/pull/6461)) - Fixed: broken user input prompt behavior on missing or non-interactive I/O streams ([#6461](https://github.com/streamlink/streamlink/pull/6461)) - Fixed: `Plugin` classes not requiring a matching matcher ([#6466](https://github.com/streamlink/streamlink/pull/6466)) - Fixed: `--hls-audio-select` not being case-insensitive and only comparing input values rather than resolved language codes ([#6469](https://github.com/streamlink/streamlink/pull/6469), [#6479](https://github.com/streamlink/streamlink/pull/6479)) - Updated: Chrome Devtool Protocol interfaces, to match recent Chromium versions ([#6481](https://github.com/streamlink/streamlink/pull/6481)) - Updated plugins: - euronews: rewritten and fixed plugin ([#6452](https://github.com/streamlink/streamlink/pull/6452)) - kick: refactored plugin, fixed clip matcher and 403 API responses ([#6491](https://github.com/streamlink/streamlink/pull/6491)) - nicolive: fixed plugin not loading cookies from WebSocket messages ([#6441](https://github.com/streamlink/streamlink/pull/6441)) - nicolive: made the plugin always filter out blank HLS segments ([#6476](https://github.com/streamlink/streamlink/pull/6476)) - nowtvtr: removed plugin ([#6488](https://github.com/streamlink/streamlink/pull/6488)) - okru: canonicalize mobile URLs ([#6444](https://github.com/streamlink/streamlink/pull/6444)) - tf1: fixed stream matcher ([#6439](https://github.com/streamlink/streamlink/pull/6439)) [Full changelog](https://github.com/streamlink/streamlink/compare/7.1.3...7.2.0) ## streamlink 7.1.3 (2025-02-14) - Fixed: `validate.contains()` to allow all kinds of `Container` object inputs ([#6421](https://github.com/streamlink/streamlink/pull/6421)) - Updated plugins: - ceskatelevize: rewritten and fixed plugin ([#6397](https://github.com/streamlink/streamlink/pull/6397)) - nos: fixed validation schema, updated matcher ([#6420](https://github.com/streamlink/streamlink/pull/6420)) - pandalive: fixed user\_id retieval, updated matcher ([#6433](https://github.com/streamlink/streamlink/pull/6433)) - pluzz: fixed video ID schemas ([#6428](https://github.com/streamlink/streamlink/pull/6428)) - streamable: removed plugin ([#6435](https://github.com/streamlink/streamlink/pull/6435)) - tv4play: removed plugin ([#6399](https://github.com/streamlink/streamlink/pull/6399)) - twitch: refactored TwitchAPI class and access token retrieval ([#6403](https://github.com/streamlink/streamlink/pull/6403)) - wwenetwork: rewritten and fixed plugin ([#6404](https://github.com/streamlink/streamlink/pull/6404)) - Docs: replaced Windows and Linux AppImage nightly builds with preview builds triggered on each commit to `master` ([#6425](https://github.com/streamlink/streamlink/pull/6425)) - Docs: added optional/secondary Linux AppImage builds with FFmpeg being bundled ([#6415](https://github.com/streamlink/streamlink/pull/6415)) - Tests: bumped `freezegun` requirement to `>=1.5.0` ([#6406](https://github.com/streamlink/streamlink/pull/6406)) [Full changelog](https://github.com/streamlink/streamlink/compare/7.1.2...7.1.3) ## streamlink 7.1.2 (2025-01-08) - Updated plugins: - various: fixed access of URL matcher regex capture groups, affecting abematv, ard\_mediathek, bbiplayer, dailymotion, picarto, streann ([#6364](https://github.com/streamlink/streamlink/pull/6364), [#6368](https://github.com/streamlink/streamlink/pull/6368)) - chzzk: added support for clips ([#6389](https://github.com/streamlink/streamlink/pull/6389)) - dailymotion: added support for lequipe.fr ([#6372](https://github.com/streamlink/streamlink/pull/6372)) - kick: fixed 403 HTTP errors, fixed VOD URL matcher ([#6384](https://github.com/streamlink/streamlink/pull/6384)) - nicolive: fixed authentication ([#6378](https://github.com/streamlink/streamlink/pull/6378)) - tiktok: rewritten plugin, fixed live streams, added VODs ([#6381](https://github.com/streamlink/streamlink/pull/6381)) [Full changelog](https://github.com/streamlink/streamlink/compare/7.1.1...7.1.2) ## streamlink 7.1.1 (2024-12-28) - Fixed: `--show-matchers=pluginname` not working when plugins are loaded lazily ([#6361](https://github.com/streamlink/streamlink/pull/6361)) [Full changelog](https://github.com/streamlink/streamlink/compare/7.1.0...7.1.1) ## streamlink 7.1.0 (2024-12-28) - Added: `--show-matchers=pluginname` CLI argument ([#6287](https://github.com/streamlink/streamlink/pull/6287)) - Updated: `Streamlink` and `Plugin` constructors to allow both `Mapping` and `Options` as `options` types ([#6311](https://github.com/streamlink/streamlink/pull/6311)) - Fixed: uncaught DASH errors if FFmpeg is unavailable ([#6306](https://github.com/streamlink/streamlink/pull/6306)) As a side effect, if FFmpeg is unavailable, DASH streams will only return one sub-stream of the video and/or audio streams that would be muxed otherwise. - Fixed: incorrect DASH segment duration in timeline manifests ([#6323](https://github.com/streamlink/streamlink/pull/6323)) - Fixed: dynamic DASH streams incorrectly requiring the `publishTime` and `availabilityStartTime` attributes ([#6324](https://github.com/streamlink/streamlink/pull/6324)) - Fixed: incorrect DASH segment and manifest base-URL joining ([#6328](https://github.com/streamlink/streamlink/pull/6328), [#6338](https://github.com/streamlink/streamlink/pull/6338)) - Fixed: `matchers` and `arguments` objects being shared in inherited `Plugin` classes ([#6297](https://github.com/streamlink/streamlink/pull/6297), [#6298](https://github.com/streamlink/streamlink/pull/6298)) - Updated plugins: - various: replaced verbose URL matcher regexes of most plugins with multiple simple ones ([#6285](https://github.com/streamlink/streamlink/pull/6285)) - bilibili: updated schema to include MPEG-TS HLS streams ([#6332](https://github.com/streamlink/streamlink/pull/6332)) - bilibili: added back high-res `HTTPStream` streams from the v1 API with higher priority ([#5782](https://github.com/streamlink/streamlink/pull/5782)) - mangomolo: replaced media.gov.kw with 51.com.kw ([#6353](https://github.com/streamlink/streamlink/pull/6353)) - soop: rewritten authentication ([#6321](https://github.com/streamlink/streamlink/pull/6321)) - vkplay: renamed to vkvideo and updated matcher ([#6319](https://github.com/streamlink/streamlink/pull/6319)) - welt: fixed schema ([#6301](https://github.com/streamlink/streamlink/pull/6301)) - Build: removed `typing-extensions` from runtime dependencies ([#6314](https://github.com/streamlink/streamlink/pull/6314)) [Full changelog](https://github.com/streamlink/streamlink/compare/7.0.0...7.1.0) ## streamlink 7.0.0 (2024-11-04) - BREAKING: dropped support for [EOL Python 3.8](https://peps.python.org/pep-0569/#lifespan) (Win 7/8 are now unsupported) ([#6230](https://github.com/streamlink/streamlink/pull/6230)) - BREAKING/CLI: [removed deprecated config file and plugin config file paths](https://streamlink.github.io/migrations.html#config-file-paths) ([#6149](https://github.com/streamlink/streamlink/pull/6149)) - BREAKING/CLI: [removed deprecated plugin sideloading paths](https://streamlink.github.io/migrations.html#custom-plugins-sideloading-paths) ([#6150](https://github.com/streamlink/streamlink/pull/6150)) - BREAKING/CLI: [removed deprecated `--force-progress` CLI argument](https://streamlink.github.io/migrations.html#force-progress) ([#6196](https://github.com/streamlink/streamlink/pull/6196)) - BREAKING/CLI: [removed deprecated stream-type related CLI arguments](https://streamlink.github.io/migrations.html#stream-type-related-cli-arguments) ([#6232](https://github.com/streamlink/streamlink/pull/6232)) - `--hls-segment-attempts` - `--hls-segment-threads` - `--hls-segment-timeout` - `--hls-timeout` - `--http-stream-timeout` - BREAKING/API: [removed deprecated stream-type related session options](https://streamlink.github.io/migrations.html#stream-type-related-cli-arguments) ([#6232](https://github.com/streamlink/streamlink/pull/6232)) - `hls-segment-attempts` - `hls-segment-threads` - `hls-segment-timeout` - `hls-timeout` - `dash-segment-attempts` - `dash-segment-threads` - `dash-segment-timeout` - `dash-timeout` - `http-stream-timeout` - BREAKING/API: [removed deprecated import paths for `HTTPSession` and `HTTPAdapter`s](https://streamlink.github.io/migrations.html#httpsession-and-httpadapters) ([#6274](https://github.com/streamlink/streamlink/pull/6274)) - BREAKING/API: [removed deprecated import paths for `NoPluginError`, `NoStreamsError`, `PluginError` and `Plugin`](https://streamlink.github.io/migrations.html#streamlink-plugins-re-exports) ([#6274](https://github.com/streamlink/streamlink/pull/6274)) - BREAKING/packaging: dropped "32 bit" Windows x86 and Linux AppImage i686 builds ([#6052](https://github.com/streamlink/streamlink/pull/6052)) - Removed: flawed implementation of VLC-specific player variables ([#6251](https://github.com/streamlink/streamlink/pull/6251), [#6253](https://github.com/streamlink/streamlink/pull/6253)) - Deprecated: [`--verbose-player` CLI argument in favor of `--player-verbose`](https://streamlink.github.io/deprecations.html#verbose-player) ([#6227](https://github.com/streamlink/streamlink/pull/6227)) - Deprecated: [`--fifo` CLI argument in favor of `--player-fifo`](https://streamlink.github.io/deprecations.html#fifo) ([#6227](https://github.com/streamlink/streamlink/pull/6227)) - Added: warning messages for deprecated/suppressed plugin arguments ([#6240](https://github.com/streamlink/streamlink/pull/6240), [#6249](https://github.com/streamlink/streamlink/pull/6249)) - Fixed: errors on missing `stdin` file descriptor ([#6239](https://github.com/streamlink/streamlink/pull/6239)) - Fixed: `--interface` not having an effect on custom `HTTPAdapter`s ([#6223](https://github.com/streamlink/streamlink/pull/6223)) - Updated plugins: - afreeca: renamed to soop, overhauled plugin and deprecated old plugin CLI arguments ([#6247](https://github.com/streamlink/streamlink/pull/6247), [#6257](https://github.com/streamlink/streamlink/pull/6257)) - ruv: rewritten and fixed plugin ([#6262](https://github.com/streamlink/streamlink/pull/6262)) - tv3cat: updated plugin matchers ([#6242](https://github.com/streamlink/streamlink/pull/6242)) - Docs: updated documentation of various CLI arguments ([#6226](https://github.com/streamlink/streamlink/pull/6226), [#6255](https://github.com/streamlink/streamlink/pull/6255), [#6225](https://github.com/streamlink/streamlink/pull/6225)) - Chore: updated typing annotations (PEP 563, PEP 585, PEP 604, PEP 613) ([#6218](https://github.com/streamlink/streamlink/pull/6218)) - Chore: reformatted the whole code base using ruff ([#6260](https://github.com/streamlink/streamlink/pull/6260)) - Build: bumped `trio` dependency to `>=0.25.0,<1` on Python >= 3.13 ([#6244](https://github.com/streamlink/streamlink/pull/6244)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.11.0...7.0.0) ## streamlink 6.11.0 (2024-10-01) - Deprecated: [`--record-and-pipe=...` in favor of `--stdout --record=...`](https://streamlink.github.io/deprecations.html#r-record-and-pipe) (and explicitly disallowed `--stdout --output=...`) ([#6194](https://github.com/streamlink/streamlink/pull/6194)) - Fixed: error when setting both `--http-no-ssl-verify` and `--http-disable-dh` ([#6205](https://github.com/streamlink/streamlink/pull/6205)) - Fixed: `--player-passthrough` without a resolved default `--player` ([#6207](https://github.com/streamlink/streamlink/pull/6207)) - Fixed: error when stdout/stderr file descriptors are missing ([#6197](https://github.com/streamlink/streamlink/pull/6197)) - Updated: webbrowser API's Chrome devtools protocol to latest version ([#6211](https://github.com/streamlink/streamlink/pull/6211)) - Updated plugins: - crunchyroll: removed plugin ([#6179](https://github.com/streamlink/streamlink/pull/6179)) - dlive: fixed missing stream URL signature ([#6171](https://github.com/streamlink/streamlink/pull/6171)) - facebook: removed plugin ([#6199](https://github.com/streamlink/streamlink/pull/6199)) - mildom: removed plugin ([#6198](https://github.com/streamlink/streamlink/pull/6198)) - tvrby: removed plugin ([#6202](https://github.com/streamlink/streamlink/pull/6202)) - tvrplus: removed plugin ([#6203](https://github.com/streamlink/streamlink/pull/6203)) - twitch: fixed client-integrity token acquirement ([#6211](https://github.com/streamlink/streamlink/pull/6211)) - vk: fixed API params and validation schema ([#6178](https://github.com/streamlink/streamlink/pull/6178)) - webtv: removed plugin ([#6172](https://github.com/streamlink/streamlink/pull/6172)) - zengatv: removed plugin ([#6174](https://github.com/streamlink/streamlink/pull/6174)) - zhanqi: removed plugin ([#6173](https://github.com/streamlink/streamlink/pull/6173)) - Tests: removed `pytest-asyncio` dependency ([#6208](https://github.com/streamlink/streamlink/pull/6208)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.10.0...6.11.0) ## streamlink 6.10.0 (2024-09-06) - Added: official support for Python 3.13 ([#6133](https://github.com/streamlink/streamlink/pull/6133)) - Added: `--logformat` and `--logdateformat` ([#6138](https://github.com/streamlink/streamlink/pull/6138), [#6144](https://github.com/streamlink/streamlink/pull/6144)) - Added: `--ffmpeg-loglevel` ([#6161](https://github.com/streamlink/streamlink/pull/6161)) - Fixed: continuous logging errors when stdout stream was closed on the reading end ([#6142](https://github.com/streamlink/streamlink/pull/6142)) - Fixed: HTTP proxy config being used when getting webbrowser API's websocket IPC address ([#6130](https://github.com/streamlink/streamlink/pull/6130)) - Updated plugins: - booyah: removed plugin ([#6162](https://github.com/streamlink/streamlink/pull/6162)) - douyin: fixed validation schema ([#6140](https://github.com/streamlink/streamlink/pull/6140)) - galatasaraytv: removed plugin ([#6163](https://github.com/streamlink/streamlink/pull/6163)) - idf1: removed plugin ([#6164](https://github.com/streamlink/streamlink/pull/6164)) - linelive: removed plugin ([#6165](https://github.com/streamlink/streamlink/pull/6165)) - pandalive: fixed missing HTTP headers ([#6148](https://github.com/streamlink/streamlink/pull/6148)) - tiktok: fixed schema of inaccessible streams ([#6160](https://github.com/streamlink/streamlink/pull/6160)) - youtube: disabled VODs, as they are currently non-functional ([#6135](https://github.com/streamlink/streamlink/pull/6135)) - Docs: bumped `sphinx-design >=0.5.0,<=0.6.1`, enabling `sphinx >=8.0.0` ([#6137](https://github.com/streamlink/streamlink/pull/6137)) - Tests: fixed `pytest-asyncio` warnings ([#6143](https://github.com/streamlink/streamlink/pull/6143)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.9.0...6.10.0) ## streamlink 6.9.0 (2024-08-12) - Added: `streamlink.plugin.api.webbrowser` subpackage with the `aws_waf` module ([#6102](https://github.com/streamlink/streamlink/pull/6102), [#6118](https://github.com/streamlink/streamlink/pull/6118)) - Added: `max_buffer_size` kwarg to `CDPClient.session()` ([#6101](https://github.com/streamlink/streamlink/pull/6101)) - Added: webbrowser `User-Agent` header override in headless mode ([#6114](https://github.com/streamlink/streamlink/pull/6114)) - Changed: default value of `--webbrowser-headless` from `True` to `False` ([#6116](https://github.com/streamlink/streamlink/pull/6116)) - Changed: unicode characters to be unescaped in JSON output, if possible ([#6080](https://github.com/streamlink/streamlink/pull/6080)) - Fixed: potential character encoding issues in Streamlink's logger ([#6081](https://github.com/streamlink/streamlink/pull/6081)) - Updated plugins: - nicolive: updated plugin matcher ([#6127](https://github.com/streamlink/streamlink/pull/6127)) - okru: fixed validation schema ([#6085](https://github.com/streamlink/streamlink/pull/6085)) - radionet: removed plugin ([#6091](https://github.com/streamlink/streamlink/pull/6091)) - sportschau: fixed plugin (HLS streams with packed audio streams remain unsupported) ([#6104](https://github.com/streamlink/streamlink/pull/6104)) - tiktok: fixed room ID validation schema ([#6106](https://github.com/streamlink/streamlink/pull/6106)) - tvp: added support for sport.tvp.pl ([#6097](https://github.com/streamlink/streamlink/pull/6097)) - twitch: added `--twitch-force-client-integrity` ([#6113](https://github.com/streamlink/streamlink/pull/6113)) - twitch: fixed broken client-integrity token decoding+parsing ([#6113](https://github.com/streamlink/streamlink/pull/6113)) - twitch: removed the `headless=False` override ([#6117](https://github.com/streamlink/streamlink/pull/6117)) - vimeo: removed error messages for unsupported DASH streams ([#6128](https://github.com/streamlink/streamlink/pull/6128)) - vk: fixed validation schema ([#6096](https://github.com/streamlink/streamlink/pull/6096)) - vtvgo: resolved AWS Web Application Firewall bot detection ([#6102](https://github.com/streamlink/streamlink/pull/6102)) - yupptv: fixed plugin, added ad filtering ([#6093](https://github.com/streamlink/streamlink/pull/6093)) - Docs: added webbrowser API metadata to plugin descriptions ([#6115](https://github.com/streamlink/streamlink/pull/6115)) - Docs: updated build-dependencies and the furo theme ([#6126](https://github.com/streamlink/streamlink/pull/6126)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.8.3...6.9.0) ## streamlink 6.8.3 (2024-07-11) Patch release: - Updated plugins: - tiktok: new plugin ([#6073](https://github.com/streamlink/streamlink/pull/6073)) - twitch: fixed channel names with uppercase characters ([#6071](https://github.com/streamlink/streamlink/pull/6071)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.8.2...6.8.3) ## streamlink 6.8.2 (2024-07-04) Patch release: - Updated plugins: - douyin: new plugin ([#6059](https://github.com/streamlink/streamlink/pull/6059)) - huya: fixed stream URLs ([#6058](https://github.com/streamlink/streamlink/pull/6058)) - pluzz: fixed API URL, stream tokens and validation schemas ([#6048](https://github.com/streamlink/streamlink/pull/6048)) - twitch: added info log messages about ad break durations ([#6051](https://github.com/streamlink/streamlink/pull/6051)) - twitch: fixed clip URLs ([#6045](https://github.com/streamlink/streamlink/pull/6045)) - twitch: fixed discontinuity warning spam in certain circumstances ([#6022](https://github.com/streamlink/streamlink/pull/6022)) - vidio: fixed stream tokens, added metadata ([#6057](https://github.com/streamlink/streamlink/pull/6057)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.8.1...6.8.2) ## streamlink 6.8.1 (2024-06-18) Patch release: - Fixed: failed HTTPAdapter tests on some OpenSSL configurations ([#6040](https://github.com/streamlink/streamlink/pull/6040), [#6042](https://github.com/streamlink/streamlink/pull/6042)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.8.0...6.8.1) ## streamlink 6.8.0 (2024-06-17) Release highlights: - Added: sha256 checksum to log message when side-loading plugins ([#6023](https://github.com/streamlink/streamlink/pull/6023)) - Added: `SSLContextAdapter` to `streamlink.session.http` ([#6024](https://github.com/streamlink/streamlink/pull/6024)) - Deprecated: [old re-exports in `streamlink.plugins` package](https://streamlink.github.io/deprecations.html#streamlink-plugins-re-exports) ([#6005](https://github.com/streamlink/streamlink/pull/6005)) - Updated plugins: - bilibili: fixed validation schema for offline channels ([#6032](https://github.com/streamlink/streamlink/pull/6032)) - chzzk: fixed channels without content ([#6002](https://github.com/streamlink/streamlink/pull/6002)) - cnbce: new plugin ([#6029](https://github.com/streamlink/streamlink/pull/6029)) - kick: new plugin ([#6012](https://github.com/streamlink/streamlink/pull/6012), [#6021](https://github.com/streamlink/streamlink/pull/6021), [#6024](https://github.com/streamlink/streamlink/pull/6024)) - tf1: added authentication via `--tf1-email` and `--tf1-password` ([#5983](https://github.com/streamlink/streamlink/pull/5983)) - tvp: fixed live streams ([#6037](https://github.com/streamlink/streamlink/pull/6037)) - welt: fixed live streams ([#6011](https://github.com/streamlink/streamlink/pull/6011)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.7.4...6.8.0) ## streamlink 6.7.4 (2024-05-12) Patch release: - Refactored: CLI errors ([#5958](https://github.com/streamlink/streamlink/pull/5958)) - Updated plugins: - afreeca: updated stream qualities ([#5953](https://github.com/streamlink/streamlink/pull/5953)) - afreeca: added `--afreeca-stream-password` ([#5952](https://github.com/streamlink/streamlink/pull/5952)) - chzzk: new plugin ([#5731](https://github.com/streamlink/streamlink/pull/5731)) - nownews: removed plugin ([#5961](https://github.com/streamlink/streamlink/pull/5961)) - turkuvaz: fixed HLS streams ([#5946](https://github.com/streamlink/streamlink/pull/5946)) - Docs: clarified plugin request rules ([#5949](https://github.com/streamlink/streamlink/pull/5949)) - Build: fixed build issues on Windows ([#5990](https://github.com/streamlink/streamlink/pull/5990)) - Build: removed `exceptiongroup` dependency on Python >= 3.11 ([#5987](https://github.com/streamlink/streamlink/pull/5987)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.7.3...6.7.4) ## streamlink 6.7.3 (2024-04-14) Patch release: - Fixed: file output paths being able to exceed max file/directory name length ([#5921](https://github.com/streamlink/streamlink/pull/5921), [#5925](https://github.com/streamlink/streamlink/pull/5925)) - Fixed: propagation of `KeyboardInterrupt`/`SystemExit` in `streamlink.webbrowser` ([#5930](https://github.com/streamlink/streamlink/pull/5930)) - Fixed: compatibility with `exceptiongroup<=1.1.1` ([#5930](https://github.com/streamlink/streamlink/pull/5930)) - Fixed: `plugin.api.validate.parse_qsd` input type validation ([#5932](https://github.com/streamlink/streamlink/pull/5932)) - Updated plugins: - mangomolo: fixed missing referer header and updated URL matcher ([#5923](https://github.com/streamlink/streamlink/pull/5923), [#5926](https://github.com/streamlink/streamlink/pull/5926)) - pluto: rewritten plugin ([#5910](https://github.com/streamlink/streamlink/pull/5910)) - showroom: fixed geo-block check preventing stream access ([#5911](https://github.com/streamlink/streamlink/pull/5911)) - vkplay: updated URL matcher ([#5908](https://github.com/streamlink/streamlink/pull/5908)) - Tests: fixed test failure when running tests from the `bdist` build directory ([#5933](https://github.com/streamlink/streamlink/pull/5933)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.7.2...6.7.3) ## streamlink 6.7.2 (2024-03-23) Patch release: - Build: reverted `trio` version requirement bump ([#5902](https://github.com/streamlink/streamlink/pull/5902)) - Build: fixed incorrect `pytest` version requirement ([#5901](https://github.com/streamlink/streamlink/pull/5901)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.7.1...6.7.2) ## streamlink 6.7.1 (2024-03-19) Patch release: - Fixed: CLI download progress missing the last data chunk ([#5887](https://github.com/streamlink/streamlink/pull/5887)) - Fixed: compatibility with `trio>=0.25` ([#5895](https://github.com/streamlink/streamlink/pull/5895)) - Updated plugins: - tv3cat: fixed plugin and added VODs ([#5890](https://github.com/streamlink/streamlink/pull/5890)) - vimeo: fixed event streams and embedded player URLs ([#5892](https://github.com/streamlink/streamlink/pull/5892), [#5899](https://github.com/streamlink/streamlink/pull/5899)) - Build: bumped `trio` dependency version requirement to `>=0.25,<1` ([#5895](https://github.com/streamlink/streamlink/pull/5895)) - Build: added `exceptiongroup` dependency ([#5895](https://github.com/streamlink/streamlink/pull/5895)) - Tests: fixed root logger level not being reset ([#5888](https://github.com/streamlink/streamlink/pull/5888), [#5897](https://github.com/streamlink/streamlink/pull/5897)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.7.0...6.7.1) ## streamlink 6.7.0 (2024-03-09) Release highlights: - Added: repeatable `--plugin-dir` CLI argument ([#5866](https://github.com/streamlink/streamlink/pull/5866)) - Deprecated: `--plugin-dirs` CLI argument with comma separated paths ([#5866](https://github.com/streamlink/streamlink/pull/5866)) - Fixed: independent encryption status of HLS initialization sections ([#5861](https://github.com/streamlink/streamlink/pull/5861)) - Fixed: objects of default session options being shared between sessions ([#5875](https://github.com/streamlink/streamlink/pull/5875)) - Updated plugins: - bloomberg: fixed data regex ([#5869](https://github.com/streamlink/streamlink/pull/5869)) - ltv_lsm_lv: fixed player ID retrieval, removed custom HLS implementation ([#5858](https://github.com/streamlink/streamlink/pull/5858)) - mangomolo: new plugin ([#5852](https://github.com/streamlink/streamlink/pull/5852)) - ustvnow: updated matcher ([#5881](https://github.com/streamlink/streamlink/pull/5881)) - vimeo: fixed optional DASH streams of live events ([#5854](https://github.com/streamlink/streamlink/pull/5854)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.6.2...6.7.0) ## streamlink 6.6.2 (2024-02-20) Patch release: - Fixed: missing plugin override log message in non-editable installs ([#5844](https://github.com/streamlink/streamlink/pull/5844)) - Fixed: incorrect `setuptools` min. version in build requirements ([#5842](https://github.com/streamlink/streamlink/pull/5842)) - Updated plugins: - afreeca: fixed broadcast number regex ([#5847](https://github.com/streamlink/streamlink/pull/5847)) - afreeca: added support for stream metadata ([#5849](https://github.com/streamlink/streamlink/pull/5849)) - hiplayer: removed media.gov.kw matcher ([#5848](https://github.com/streamlink/streamlink/pull/5848)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.6.1...6.6.2) ## streamlink 6.6.1 (2024-02-17) Patch release: - Fixed: plugin arguments in `--help` output ([#5838](https://github.com/streamlink/streamlink/pull/5838)) - Docs: removed empty plugin sections in docs and man page ([#5838](https://github.com/streamlink/streamlink/pull/5838)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.6.0...6.6.1) ## streamlink 6.6.0 (2024-02-16) Release highlights: - Implemented: lazy plugins loading ([#5793](https://github.com/streamlink/streamlink/pull/5793), [#5822](https://github.com/streamlink/streamlink/pull/5822)) Streamlink's built-in plugins will now be loaded on demand when resolving input URLs. This improves initial loading times and reduces total memory consumption. - Refactored: Streamlink session module (and related) - Moved: `streamlink.session` into a sub-package ([#5807](https://github.com/streamlink/streamlink/pull/5807)) - Moved: `streamlink.plugin.api.http_session` to `streamlink.session.http` ([#5807](https://github.com/streamlink/streamlink/pull/5807)) - Refactored: `Streamlink` class internals ([#5807](https://github.com/streamlink/streamlink/pull/5807), [#5814](https://github.com/streamlink/streamlink/pull/5814)) - Deprecated: [`Streamlink.{get,load}_plugins()` methods](https://streamlink.github.io/deprecations.html#streamlink-get-load-plugins) ([#5818](https://github.com/streamlink/streamlink/pull/5818)) - Deprecated: [direct imports of `HTTPSession` and imports from `streamlink.plugin.api.http_session`](https://streamlink.github.io/deprecations.html#httpsession-and-httpadapters) ([#5818](https://github.com/streamlink/streamlink/pull/5818)) - Refactored: `streamlink.utils.args` module ([#5778](https://github.com/streamlink/streamlink/pull/5778), [#5781](https://github.com/streamlink/streamlink/pull/5781), [#5815](https://github.com/streamlink/streamlink/pull/5815)) - Updated plugins: - aloula: fixed missing HTTP headers ([#5792](https://github.com/streamlink/streamlink/pull/5792)) - foxtr: removed plugin ([#5827](https://github.com/streamlink/streamlink/pull/5827)) - huya: fixed stream URLs ([#5785](https://github.com/streamlink/streamlink/pull/5785)) - nowtvtr: new plugin ([#5827](https://github.com/streamlink/streamlink/pull/5827)) - qq: removed plugin ([#5806](https://github.com/streamlink/streamlink/pull/5806)) - rtbf: removed plugin ([#5801](https://github.com/streamlink/streamlink/pull/5801)) - Tests: improved overall test execution time ([#5799](https://github.com/streamlink/streamlink/pull/5799), [#5805](https://github.com/streamlink/streamlink/pull/5805)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.5.1...6.6.0) ## streamlink 6.5.1 (2024-01-16) Patch release: - Fixed: file output path log message on py38/py39 ([#5728](https://github.com/streamlink/streamlink/pull/5728)) - Improved: warning message when using quoted player paths (in config files) ([#5757](https://github.com/streamlink/streamlink/pull/5757)) - Updated plugins: - artetv: updated API response validation schema ([#5774](https://github.com/streamlink/streamlink/pull/5774)) - atresplayer: updated API response validation schema ([#5742](https://github.com/streamlink/streamlink/pull/5742)) - bigo: reimplemented plugin ([#5754](https://github.com/streamlink/streamlink/pull/5754)) - bilibili: fixed stream resolving issues on channels with custom layouts ([#5771](https://github.com/streamlink/streamlink/pull/5771)) - huya: added stream CDN availability check ([#5745](https://github.com/streamlink/streamlink/pull/5745)) - twitch: disabled Chromium headless mode on client-integrity token acquirement ([#5758](https://github.com/streamlink/streamlink/pull/5758)) - vidio: fixed missing API request cookies ([#5762](https://github.com/streamlink/streamlink/pull/5762)) - zattoo: fixed audio/video sync issues ([#5739](https://github.com/streamlink/streamlink/pull/5739)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.5.0...6.5.1) ## streamlink 6.5.0 (2023-12-16) Release highlights: - Fixed: `UserWarning` being emitted by recent `pycountry` releases when parsing certain language codes ([#5722](https://github.com/streamlink/streamlink/pull/5722)) - Fixed: trace logging setup in `WebsocketClient` implementation ([#5705](https://github.com/streamlink/streamlink/pull/5705)) - Updated plugins: - btv: switched to HLS multivariant playlists ([#5698](https://github.com/streamlink/streamlink/pull/5698)) - gulli: rewritten plugin ([#5725](https://github.com/streamlink/streamlink/pull/5725)) - twitch: removed/disabled `--twitch-disable-reruns` ([#5704](https://github.com/streamlink/streamlink/pull/5704)) - twitch: enabled `check_streams` HLS option, to ensure early stream availability without querying the delayed Twitch API ([#5708](https://github.com/streamlink/streamlink/pull/5708)) - twitch: removed unnecessary Twitch API error messages for offline channels ([#5709](https://github.com/streamlink/streamlink/pull/5709)) - wasd: removed plugin ([#5711](https://github.com/streamlink/streamlink/pull/5711)) - Build: added support for `versioningit >=3.0.0`, with backward compatibility ([#5721](https://github.com/streamlink/streamlink/pull/5721)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.4.2...6.5.0) ## streamlink 6.4.2 (2023-11-28) Patch release: - Fixed: HLS segment maps being written to the output multiple times ([#5689](https://github.com/streamlink/streamlink/pull/5689)) - Fixed plugins: - bilibili: rewritten plugin ([#5693](https://github.com/streamlink/streamlink/pull/5693)) - piczel: updated HLS URLs ([#5690](https://github.com/streamlink/streamlink/pull/5690)) - ssh101: fixed stream URL retrieval ([#5686](https://github.com/streamlink/streamlink/pull/5686)) - Docs: added missing `Cache` API docs ([#5688](https://github.com/streamlink/streamlink/pull/5688)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.4.1...6.4.2) ## streamlink 6.4.1 (2023-11-22) Patch release: - Fixed: libxml2 2.12.0 compatibility ([#5682](https://github.com/streamlink/streamlink/pull/5682)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.4.0...6.4.1) ## streamlink 6.4.0 (2023-11-21) Release highlights: - Added: missing support for dynamic DASH manifests with `SegmentList`s ([#5654](https://github.com/streamlink/streamlink/pull/5654), [#5657](https://github.com/streamlink/streamlink/pull/5657)) - Added: warning log message when skipping DASH segments between manifest reloads ([#5659](https://github.com/streamlink/streamlink/pull/5659)) - Added plugins: nasaplus ([#5664](https://github.com/streamlink/streamlink/pull/5664)) - Updated plugins: - raiplay: added VOD support with authentication `--raiplay-email` / `--raiplay-password` / `--raiplay-purge-credentials` ([#5662](https://github.com/streamlink/streamlink/pull/5662)) - telemadrid: fixed XPath query ([#5653](https://github.com/streamlink/streamlink/pull/5653)) - tvp: fixed tvp.info ([#5645](https://github.com/streamlink/streamlink/pull/5645)) - youtube: fixed video ID retrieval ([#5673](https://github.com/streamlink/streamlink/pull/5673)) - Docs: added validation schema API docs and API guide ([#5652](https://github.com/streamlink/streamlink/pull/5652), [#5655](https://github.com/streamlink/streamlink/pull/5655)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.3.1...6.4.0) ## streamlink 6.3.1 (2023-10-26) Patch release: - Fixed plugins: - welt: rewritten plugin ([#5637](https://github.com/streamlink/streamlink/pull/5637)) - Build: fixed tests when running from sdist ([#5636](https://github.com/streamlink/streamlink/pull/5636)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.3.0...6.3.1) ## streamlink 6.3.0 (2023-10-25) Release highlights: - Added: warning log message when skipping HLS segments between playlist reloads ([#5607](https://github.com/streamlink/streamlink/pull/5607)) - Refactored: internals of segmented stream implementations (base classes, HLS, DASH) - Added: base `Segment` dataclass and made segmented streams inherit from it ([#5594](https://github.com/streamlink/streamlink/pull/5594)) - Moved: modules into sub-packages (import paths of public APIs remain the same) ([#5593](https://github.com/streamlink/streamlink/pull/5593)) - Renamed: various non-public HLS class methods/attributes and functions ([#5526](https://github.com/streamlink/streamlink/pull/5526)) - Removed: `Sequence` segment wrapper from HLS implementation ([#5526](https://github.com/streamlink/streamlink/pull/5526)) - Fixed: DASH manifest not respecting the `minBufferTime` ([#5610](https://github.com/streamlink/streamlink/pull/5610)) - Fixed: URL matchers of HLS/DASH protocol plugins ([#5616](https://github.com/streamlink/streamlink/pull/5616), [#5617](https://github.com/streamlink/streamlink/pull/5617)) - Fixed: bandwidth parsing issue in HLS multivariant playlists ([#5619](https://github.com/streamlink/streamlink/pull/5619)) - Fixed plugins: - dlive: fixed live streams and fixed VODs ([#5622](https://github.com/streamlink/streamlink/pull/5622), [#5623](https://github.com/streamlink/streamlink/pull/5623)) - goodgame: rewritten plugin using goodgame API v4 ([#5586](https://github.com/streamlink/streamlink/pull/5586), [#5597](https://github.com/streamlink/streamlink/pull/5597)) - mitele: updated gbx API calls from v2 to v3 ([#5624](https://github.com/streamlink/streamlink/pull/5624)) - twitch: fixed error handling of geo-restricted or inaccessible streams ([#5591](https://github.com/streamlink/streamlink/pull/5591)) - Removed plugins: - ntv: static stream URLs ([#5604](https://github.com/streamlink/streamlink/pull/5604)) - vlive: offline ([#5599](https://github.com/streamlink/streamlink/pull/5599)) - Build: dropped `versioningit` build-requirement when building from sdist tarball (version string has always been built-in while `versioningit` performed a no-op) ([#5632](https://github.com/streamlink/streamlink/pull/5632)) - Packaging: added missing shell completions build-script to sdist ([#5625](https://github.com/streamlink/streamlink/pull/5625)) - Docs: clarified section about building from source (sdist/git vs. GitHub tarballs) ([#5633](https://github.com/streamlink/streamlink/pull/5633)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.2.1...6.3.0) ## streamlink 6.2.1 (2023-10-03) Patch release: - Added: official support for Python 3.12 ([#5576](https://github.com/streamlink/streamlink/pull/5576)) - Fixed plugins: goodgame ([#5557](https://github.com/streamlink/streamlink/pull/5557)), nos ([#5565](https://github.com/streamlink/streamlink/pull/5565)), pandalive ([#5569](https://github.com/streamlink/streamlink/pull/5569)), wwenetwork ([#5559](https://github.com/streamlink/streamlink/pull/5559)) - Build: added custom setuptools build-backend override which fixes issues with building Windows-specific wheels ([#5558](https://github.com/streamlink/streamlink/pull/5558)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.2.0...6.2.1) ## streamlink 6.2.0 (2023-09-14) Release highlights: - Added: `--player-env` CLI argument ([#5535](https://github.com/streamlink/streamlink/pull/5535)) - Added: OpenSSL version to debug log output ([#5506](https://github.com/streamlink/streamlink/pull/5506)) - Updated: segmented stream internals and typing ([#5504](https://github.com/streamlink/streamlink/pull/5504), [#5507](https://github.com/streamlink/streamlink/pull/5507)) - Updated: internal HLS tag parsing setup and parser state ([#5513](https://github.com/streamlink/streamlink/pull/5513), [#5521](https://github.com/streamlink/streamlink/pull/5521)) - Fixed: HLS streams not ending on playlist reload with endlist tag and no new segments ([#5538](https://github.com/streamlink/streamlink/pull/5538)) - Fixed: missing file encoding when writing a log file ([#5532](https://github.com/streamlink/streamlink/pull/5532)) - Added plugins: piaulizaportal ([#5508](https://github.com/streamlink/streamlink/pull/5508)) - Fixed plugins: hiplayer ([#5534](https://github.com/streamlink/streamlink/pull/5534)), nicolive ([#5529](https://github.com/streamlink/streamlink/pull/5529)), pluto ([#5551](https://github.com/streamlink/streamlink/pull/5551)) - Docs: added list of supported metadata variables for each plugin ([#5517](https://github.com/streamlink/streamlink/pull/5517), [#5519](https://github.com/streamlink/streamlink/pull/5519)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.1.0...6.2.0) ## streamlink 6.1.0 (2023-08-16) Release highlights: - Added: `--hls-segment-queue-threshold` for being able to configure when to stop HLS streams early on missing segments ([#5478](https://github.com/streamlink/streamlink/pull/5478)) - Fixed: config file parsing issues and made parsing argument values more strict ([#5484](https://github.com/streamlink/streamlink/pull/5484)) - Fixed: race condition when reading and validating the FFmpeg version string ([#5480](https://github.com/streamlink/streamlink/pull/5480)) - Fixed plugins: atresplayer ([#5477](https://github.com/streamlink/streamlink/pull/5477)) - Docs: added code examples for the [removal of `Streamlink.{g,s}et_plugin_option`](https://streamlink.github.io/migrations.html#streamlink-g-s-et-plugin-option) ([#5497](https://github.com/streamlink/streamlink/pull/5497)) - Build: fixed entry-points config issues with setuptools `68.1.0` ([#5500](https://github.com/streamlink/streamlink/pull/5500)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.0.1...6.1.0) ## streamlink 6.0.1 (2023-08-02) Patch release: - Added: missing `options` argument to `Streamlink.streams()` ([#5469](https://github.com/streamlink/streamlink/pull/5469)) - Fixed: migration docs and the [`6.0.0` changelog](https://streamlink.github.io/changelog.html#streamlink-6-0-0-2023-07-20) of the [`Streamlink.{g,s}et_plugin_option()` removal](https://streamlink.github.io/migrations.html#streamlink-g-s-et-plugin-option) ([#5471](https://github.com/streamlink/streamlink/pull/5471)) - Fixed plugins: huya ([#5467](https://github.com/streamlink/streamlink/pull/5467)) - Docs: updated build-dependencies and the furo theme ([#5464](https://github.com/streamlink/streamlink/pull/5464), [#5465](https://github.com/streamlink/streamlink/pull/5465)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.0.0...6.0.1) ## streamlink 6.0.0 (2023-07-20) Breaking changes: - BREAKING: dropped support for Python 3.7 ([#5302](https://github.com/streamlink/streamlink/pull/5302)) - BREAKING: [turned `--player` CLI argument into a player-path-only argument](https://streamlink.github.io/migrations.html#player-path-only-player-cli-argument) ([#5305](https://github.com/streamlink/streamlink/issues/5305), [#5310](https://github.com/streamlink/streamlink/pull/5310)) Its value won't be interpreted as a command line string anymore, so paths with whitespace don't require additional quotation. Custom player arguments now always need to be set via `--player-args`. - BREAKING: [removed deprecated `{filename}` variable from `--player-args`](https://streamlink.github.io/migrations.html#filename-variable-in-player-args) ([#5310](https://github.com/streamlink/streamlink/pull/5310)) - BREAKING/API: [removed support for the deprecated `Plugin.can_handle_url()` / `Plugin.priority()` classmethods](https://streamlink.github.io/migrations.html#plugin-can-handle-url-and-plugin-priority) ([#5403](https://github.com/streamlink/streamlink/pull/5403)) - BREAKING/API: [removed deprecated compatibility wrapper for the `Plugin` constructor](https://streamlink.github.io/migrations.html#plugin-init-self-url-compatibility-wrapper) ([#5402](https://github.com/streamlink/streamlink/pull/5402)) - BREAKING/API: [removed `Streamlink.{g,s}et_plugin_option()`](https://streamlink.github.io/migrations.html#streamlink-g-s-et-plugin-option) ([#5033](https://github.com/streamlink/streamlink/pull/5033)) - BREAKING/API: [removed deprecated global plugin arguments](https://streamlink.github.io/migrations.html#global-plugin-arguments) ([#5033](https://github.com/streamlink/streamlink/pull/5033)) - BREAKING/API: [removed deprecated `streamlink.plugin.api.validate.text`](https://streamlink.github.io/migrations.html#plugin-api-validate-text) ([#5404](https://github.com/streamlink/streamlink/pull/5404)) - BREAKING/API: [fixed/changed signatures of `HTTPStream`, `HLSStream` and `HLSStream.parse_variant_playlist()`](https://streamlink.github.io/migrations.html#httpstream-and-hlsstream-signature-changes) ([#5429](https://github.com/streamlink/streamlink/pull/5429)) - BREAKING/packaging: new signing key [`44448A298D5C3618`](https://keyserver.ubuntu.com/pks/lookup?search=44448A298D5C3618&fingerprint=on&op=index) ([#5449](https://github.com/streamlink/streamlink/pull/5449)) Release highlights: - Added: experimental `streamlink.webbrowser` API for extracting data from websites using the system's Chromium-based web browser ([#5380](https://github.com/streamlink/streamlink/issues/5380), [#5381](https://github.com/streamlink/streamlink/pull/5381), [#5386](https://github.com/streamlink/streamlink/pull/5386), [#5388](https://github.com/streamlink/streamlink/pull/5388), [#5410](https://github.com/streamlink/streamlink/pull/5410)) See the [`--webbrowser`, `--webbrowser-executable` and related CLI arguments](https://streamlink.github.io/cli.html#web-browser-options) for more - Added: client-integrity token support to Twitch plugin using the `streamlink.webbrowser` API (currently only used as a fallback when acquiring the access token fails) ([#5414](https://github.com/streamlink/streamlink/pull/5414)) - Added: `{playertitleargs}` variable to `--player-args` ([#5310](https://github.com/streamlink/streamlink/pull/5310)) - Added: `with_{video,audio}_only` parameters to `DASHStream.parse_manifest()` ([#5340](https://github.com/streamlink/streamlink/pull/5340)) - Changed: HLS streams to stop early on missing `EXT-X-ENDLIST` tag when polling the playlist doesn't yield new segments for twice its targetduration value ([#5330](https://github.com/streamlink/streamlink/pull/5330)) - Fixed: regex of optional protocol plugin parameters ([#5367](https://github.com/streamlink/streamlink/pull/5367)) - Fixed plugins: lrt ([#5444](https://github.com/streamlink/streamlink/pull/5444)), mediavitrina ([#5376](https://github.com/streamlink/streamlink/pull/5376)), mitele ([#5436](https://github.com/streamlink/streamlink/pull/5436)), NRK ([#5408](https://github.com/streamlink/streamlink/pull/5408)), pluzz ([#5369](https://github.com/streamlink/streamlink/pull/5369)), rtvs ([#5443](https://github.com/streamlink/streamlink/pull/5443)), showroom ([#5390](https://github.com/streamlink/streamlink/pull/5390)), turkuvaz ([#5374](https://github.com/streamlink/streamlink/pull/5374)), vimeo ([#5335](https://github.com/streamlink/streamlink/pull/5335)), youtube ([#5351](https://github.com/streamlink/streamlink/pull/5351)) - Docs: added migrations page for further guidance on resolving breaking changes ([#5433](https://github.com/streamlink/streamlink/pull/5433)) - Docs: split up, updated and improved API docs ([#5398](https://github.com/streamlink/streamlink/pull/5398)) - Build: moved project metadata to pyproject.toml (PEP621) ([#5438](https://github.com/streamlink/streamlink/pull/5438)) - Dependencies: added `trio` ([#5386](https://github.com/streamlink/streamlink/pull/5386)), `trio-websocket` and `typing-extensions` ([#5388](https://github.com/streamlink/streamlink/pull/5388)), and removed `importlib_metadata` ([#5302](https://github.com/streamlink/streamlink/pull/5302)) [Full changelog](https://github.com/streamlink/streamlink/compare/5.5.1...6.0.0) ## streamlink 5.5.1 (2023-05-08) Patch release: - Fixed: shifting time offset when reloading HLS playlists ([#5321](https://github.com/streamlink/streamlink/pull/5321)) - Fixed: import of `create_urllib3_context` on `urllib3 <2.0.0` ([#5333](https://github.com/streamlink/streamlink/pull/5333)) - Fixed: Vimeo plugin ([#5331](https://github.com/streamlink/streamlink/pull/5331)) [Full changelog](https://github.com/streamlink/streamlink/compare/5.5.0...5.5.1) ## streamlink 5.5.0 (2023-05-05) Release highlights: - Added: `--no-config` ([#5314](https://github.com/streamlink/streamlink/pull/5314)) - Added: `--player-external-http-interface` ([#5295](https://github.com/streamlink/streamlink/pull/5295)) - Fixed: M3U8 attribute parsing issue ([#5307](https://github.com/streamlink/streamlink/pull/5307)) - Fixed: various minor plugin issues ([#5291](https://github.com/streamlink/streamlink/pull/5291), [#5299](https://github.com/streamlink/streamlink/pull/5299), [#5306](https://github.com/streamlink/streamlink/pull/5306)) - Build: bumped urllib3 to `>=1.26.0,<3` and fixed compatibility issues with `urllib3 >=2.0.0` ([#5326](https://github.com/streamlink/streamlink/pull/5326), [#5325](https://github.com/streamlink/streamlink/pull/5325)) - Docs: bumped furo theme to `2023.03.27` ([#5301](https://github.com/streamlink/streamlink/pull/5301)) - Docs: bumped build dependencies `sphinx >=5.0.0,<7`, `myst-parser >=1.0.0,<2` and `sphinx-design >=0.4.1,<1` ([#5301](https://github.com/streamlink/streamlink/pull/5301)) [Full changelog](https://github.com/streamlink/streamlink/compare/5.4.0...5.5.0) ## streamlink 5.4.0 (2023-04-12) Release highlights: - Added: `--progress` CLI argument and [deprecated `--force-progress`](https://streamlink.github.io/deprecations.html#deprecation-of-force-progress) ([#5268](https://github.com/streamlink/streamlink/pull/5268)) - Added: `--dash-manifest-reload-attempts` and respective session option ([#5208](https://github.com/streamlink/streamlink/pull/5208)) - Improved: DASH segment availability/download logging ([#5214](https://github.com/streamlink/streamlink/pull/5214), [#5235](https://github.com/streamlink/streamlink/pull/5235)) - Refactored: DASH parser + stream implementation ([#5221](https://github.com/streamlink/streamlink/pull/5221), [#5224](https://github.com/streamlink/streamlink/pull/5224), [#5225](https://github.com/streamlink/streamlink/pull/5225), [#5244](https://github.com/streamlink/streamlink/pull/5244), [#5248](https://github.com/streamlink/streamlink/pull/5248)) - Fixed: DASH segment template numbers and availability times ([#5213](https://github.com/streamlink/streamlink/pull/5213), [#5217](https://github.com/streamlink/streamlink/pull/5217), [#5233](https://github.com/streamlink/streamlink/pull/5233)) - Fixed: DASH manifest mediaPresentationDuration and period duration ([#5226](https://github.com/streamlink/streamlink/pull/5226)) - Fixed: DASH manifest suggestedPresentationDelay ([#5215](https://github.com/streamlink/streamlink/pull/5215)) - Fixed: various DASH manifest parsing bugs ([#5247](https://github.com/streamlink/streamlink/pull/5247)) - Fixed: DASH timeline IDs not being unique ([#5199](https://github.com/streamlink/streamlink/pull/5199)) - Fixed: DASH substreams not having synced timelines ([#5262](https://github.com/streamlink/streamlink/pull/5262)) - Fixed: queued DASH segments being downloaded after closing the stream ([#5236](https://github.com/streamlink/streamlink/pull/5236), [#5237](https://github.com/streamlink/streamlink/pull/5237)) - Fixed: incorrect min/max values of certain numeric CLI arguments ([#5239](https://github.com/streamlink/streamlink/pull/5239)) - Fixed: all naive datetime objects and made them timezone-aware ([#5210](https://github.com/streamlink/streamlink/pull/5210)) - Fixed: TV5monde plugin with new implementation ([#5206](https://github.com/streamlink/streamlink/pull/5206)) - Fixed: Steam plugin missing CDN auth data in stream URLs ([#5222](https://github.com/streamlink/streamlink/pull/5222)) - Fixed: Vimeo plugin's playerConfig regex ([#5227](https://github.com/streamlink/streamlink/pull/5227)) - Fixed: VKplay plugin's validation schema ([#5251](https://github.com/streamlink/streamlink/pull/5251)) - Fixed: Twitcasting plugin with new implementation ([#5255](https://github.com/streamlink/streamlink/pull/5255)) - Tests: fixed setuptools/pkg\_resources DeprecationWarnings ([#5167](https://github.com/streamlink/streamlink/pull/5167), [#5230](https://github.com/streamlink/streamlink/pull/5230)) - Tests: fixed ResourceWarnings due to stale file handles ([#5242](https://github.com/streamlink/streamlink/pull/5242)) - Added plugins: indihometv ([#5266](https://github.com/streamlink/streamlink/pull/5266)), telemadrid ([#5212](https://github.com/streamlink/streamlink/pull/5212)) - Removed plugins: nbcnews ([#5279](https://github.com/streamlink/streamlink/pull/5279)), useetv ([#5266](https://github.com/streamlink/streamlink/pull/5266)) [Full changelog](https://github.com/streamlink/streamlink/compare/5.3.1...5.4.0) ## streamlink 5.3.1 (2023-02-25) Patch release: - Fixed: `http-trust-env` session option name (`--http-ignore-env` CLI parameter) ([#5193](https://github.com/streamlink/streamlink/pull/5193)) - Fixed: missing byterange attribute of initialization segments in DASH streams ([#5189](https://github.com/streamlink/streamlink/pull/5189)) - Fixed: broken BaseURL context in DASH streams ([#5194](https://github.com/streamlink/streamlink/pull/5194)) - Fixed: detection of certain encrypted DASH streams ([#5196](https://github.com/streamlink/streamlink/pull/5196)) [Full changelog](https://github.com/streamlink/streamlink/compare/5.3.0...5.3.1) ## streamlink 5.3.0 (2023-02-18) Release highlights: - Project meta: dropped Open Collective sponsoring platform and updated the project's README, as well as the docs' donation/support page ([#5143](https://github.com/streamlink/streamlink/pull/5143)) - Deprecated: global plugin arguments ([#5140](https://github.com/streamlink/streamlink/pull/5140)) - Fixed: muxed streams sometimes missing data at the end ([#5162](https://github.com/streamlink/streamlink/pull/5162)) - Fixed: named pipes sometimes not being cleaned up properly ([#5162](https://github.com/streamlink/streamlink/pull/5162)) - Fixed: new YouTube channel URLs not being matched ([#5137](https://github.com/streamlink/streamlink/pull/5137)) - Fixed: KeyError when accessing certain YouTube URLs ([#5139](https://github.com/streamlink/streamlink/pull/5139)) - Fixed: M3U8 attribute parsing ([#5125](https://github.com/streamlink/streamlink/pull/5125)) - Fixed: NimoTV streams stopping after a few seconds ([#5147](https://github.com/streamlink/streamlink/pull/5147)) - Fixed: delimiter of `http-query-params` session option string setter ([#5176](https://github.com/streamlink/streamlink/pull/5176)) - Fixed: sdist/bdist missing some files ([#5119](https://github.com/streamlink/streamlink/pull/5119), [#5141](https://github.com/streamlink/streamlink/pull/5141)) - Docs: fixed `Streamlink.set_option()` docstring ([#5176](https://github.com/streamlink/streamlink/pull/5176)) - Docs: improved CLI tutorial ([#5157](https://github.com/streamlink/streamlink/pull/5157)) - Docs: improved install page ([#5178](https://github.com/streamlink/streamlink/pull/5178)) - Removed plugins: funimationnow ([#5128](https://github.com/streamlink/streamlink/pull/5128)), schoolism ([#5127](https://github.com/streamlink/streamlink/pull/5127)) [Full changelog](https://github.com/streamlink/streamlink/compare/5.2.1...5.3.0) ## streamlink 5.2.1 (2023-01-23) No code changes. Please see the changelog of the [`5.2.0`](https://streamlink.github.io/changelog.html#streamlink-5-2-0-2023-01-23) release. - Reverted: PyPI deploy script changes ([#5116](https://github.com/streamlink/streamlink/pull/5116)) [Full changelog](https://github.com/streamlink/streamlink/compare/5.2.0...5.2.1) ## streamlink 5.2.0 (2023-01-23) Release highlights: - Added: new stream read/output loop, to be able to detect player process termination while stream output is paused (ad filtering, etc.) ([#5024](https://github.com/streamlink/streamlink/pull/5024)) - Added: support for named plugin matchers ([#5103](https://github.com/streamlink/streamlink/pull/5103), [#5107](https://github.com/streamlink/streamlink/pull/5107)) - Added: Python warnings capturing to streamlink logger and added `StreamlinkWarning` ([#5072](https://github.com/streamlink/streamlink/pull/5072)) - Changed: deprecation log messages to warnings, and added missing warnings for [previously deprecated things](https://streamlink.github.io/deprecations.html) ([#5072](https://github.com/streamlink/streamlink/pull/5072)) - Deprecated: usage of `validate.text` in favor of `str` ([#5090](https://github.com/streamlink/streamlink/pull/5090)) - Improved: `Streamlink` session option getters/setters ([#5076](https://github.com/streamlink/streamlink/pull/5076)) - Fixed: incorrect inheritance of `NoPluginError` and removed unneeded `url` parameter from `NoStreamsError` ([#5088](https://github.com/streamlink/streamlink/pull/5088)) - Fixed: error handling in Twitch access token acquirement ([#5011](https://github.com/streamlink/streamlink/pull/5011)) - Fixed: dogan plugin ([#5053](https://github.com/streamlink/streamlink/pull/5053)) - Fixed: ceskatelevize plugin, added sport/sportplus/decko ([#5063](https://github.com/streamlink/streamlink/pull/5063)) - Added plugins: mixcloud ([#5096](https://github.com/streamlink/streamlink/pull/5096)), vkplay ([#5054](https://github.com/streamlink/streamlink/pull/5054)) - Removed plugins: orf_tvthek ([#5104](https://github.com/streamlink/streamlink/pull/5104)) [Full changelog](https://github.com/streamlink/streamlink/compare/5.1.2...5.2.0) ## streamlink 5.1.2 (2022-12-03) Patch release: - Fixed: `ValueError` being raised while muxing streams ([#4998](https://github.com/streamlink/streamlink/pull/4998)) - Fixed: ad filtering bug in Twitch plugin ([#5007](https://github.com/streamlink/streamlink/pull/5007)) - Fixed: SVTPlay plugin ([#4994](https://github.com/streamlink/streamlink/pull/4994)) - Fixed: TVP plugin ([#4997](https://github.com/streamlink/streamlink/pull/4997)) - Docs: updated Linux AppImage and Windows builds install sections ([#4999](https://github.com/streamlink/streamlink/pull/4999)) - Docs: fixed man page links in HTML docs ([#4995](https://github.com/streamlink/streamlink/pull/4995)) [Full changelog](https://github.com/streamlink/streamlink/compare/5.1.1...5.1.2) ## streamlink 5.1.1 (2022-11-23) Patch release: - Changed: `WebsocketClient` to use CA certificates bundled by `certifi` ([#4977](https://github.com/streamlink/streamlink/pull/4977)) - Fixed: `SegmentedStreamReader` not properly getting closed ([#4972](https://github.com/streamlink/streamlink/pull/4972)) - Fixed: CLI argument links throughout the entire docs ([#4989](https://github.com/streamlink/streamlink/pull/4989)) - Build: added `certifi` as a direct dependency ([#4977](https://github.com/streamlink/streamlink/pull/4977)) [Full changelog](https://github.com/streamlink/streamlink/compare/5.1.0...5.1.1) ## streamlink 5.1.0 (2022-11-14) Release highlights: - Added: debug log messages of the FFmpeg version in use ([#4861](https://github.com/streamlink/streamlink/pull/4861)) Checking the FFmpeg version can be disabled via `--ffmpeg-no-validation` - Added: `--twitch-access-token-param` for changing access token API request params ([#4952](https://github.com/streamlink/streamlink/pull/4952)) - Added: new log level `all` ([#4941](https://github.com/streamlink/streamlink/pull/4941)) - Updated: sbscokr plugin and removed the `--sbscokr-id` parameter ([#4865](https://github.com/streamlink/streamlink/pull/4865)) - Updated: Twitch authentication docs ([#4940](https://github.com/streamlink/streamlink/pull/4940), [#4956](https://github.com/streamlink/streamlink/pull/4956)) - Fixed: broken `--twitch-disable-ads` mid-roll ad filering ([#4942](https://github.com/streamlink/streamlink/pull/4942)) - Fixed: incorrect module name for trace logs on Python 3.11 ([#4863](https://github.com/streamlink/streamlink/pull/4863)) - Fixed: bloomberg plugin ([#4919](https://github.com/streamlink/streamlink/pull/4919)) - Fixed: dailymotion plugin ([#4910](https://github.com/streamlink/streamlink/pull/4910)) - Fixed: raiplay plugin ([#4851](https://github.com/streamlink/streamlink/pull/4851)) - Fixed: tvp plugin ([#4905](https://github.com/streamlink/streamlink/pull/4905)) - Fixed: vinhlongtv plugin ([#4850](https://github.com/streamlink/streamlink/pull/4850)) - Fixed: various other plugin issues (see full changelog) - Removed plugins: egame ([#4866](https://github.com/streamlink/streamlink/pull/4866)) - Build: added `urllib3` as a direct dependency and set it to `>=1.26.0` ([#4950](https://github.com/streamlink/streamlink/pull/4950)) - Build: added `pytest-asyncio` to dev-requirements ([#4861](https://github.com/streamlink/streamlink/pull/4861)) [Full changelog](https://github.com/streamlink/streamlink/compare/5.0.1...5.1.0) ## streamlink 5.0.1 (2022-09-22) Patch release: - Fixed: truncation of relative paths in progress output on Windows ([#4830](https://github.com/streamlink/streamlink/pull/4830)) - Fixed: mitele plugin's validation schema ([#4839](https://github.com/streamlink/streamlink/pull/4839)) - Fixed: infinite loop in rtve plugin ([#4840](https://github.com/streamlink/streamlink/pull/4840)) [Full changelog](https://github.com/streamlink/streamlink/compare/5.0.0...5.0.1) ## streamlink 5.0.0 (2022-09-16) Breaking changes: - BREAKING: removed `avconv` (libav) from FFmpeg fallback list ([#4826](https://github.com/streamlink/streamlink/pull/4826)) - BREAKING/API: removed `Plugin.bind()` and changed the signature of the `Plugin` class constructor ([#4768](https://github.com/streamlink/streamlink/pull/4768)) A compatibility wrapper for these interface changes has temporarily been added in order to keep third-party plugin implementations working. [Please see the deprecation docs for more details.](https://streamlink.github.io/deprecations.html#deprecation-of-plugin-init-self-url) - BREAKING/API: changed the return value of `Session.resolve_url()` ([#4768](https://github.com/streamlink/streamlink/pull/4768)) [Please see the deprecation docs for more details.](https://streamlink.github.io/deprecations.html#session-resolve-url-return-type-changes) - BREAKING/API: removed `HTTPSession.parse_*()` methods ([#4803](https://github.com/streamlink/streamlink/pull/4803)) Release highlights: - Added: official support for Python 3.11 ([#4806](https://github.com/streamlink/streamlink/pull/4806)) - Added: `--player-external-http-continuous` ([#4739](https://github.com/streamlink/streamlink/pull/4739)) - Added: file path to progress output (`--output`, `--record`, etc.) ([#4764](https://github.com/streamlink/streamlink/pull/4764)) - Added: warning message when FFmpeg is not available and muxing is unsupported ([#4781](https://github.com/streamlink/streamlink/pull/4781)) - Changed: logging channel of deprecation messages to "warning" ([#4785](https://github.com/streamlink/streamlink/pull/4785)) - Disabled: `--twitch-disable-hosting` and removed its logic ([#4805](https://github.com/streamlink/streamlink/pull/4805)) - Fixed: memory leak when initializing the `Streamlink` session ([#4768](https://github.com/streamlink/streamlink/pull/4768)) - Fixed: cbsnews plugin ([#4743](https://github.com/streamlink/streamlink/pull/4743)) - Fixed: steam plugin authentication ([#4745](https://github.com/streamlink/streamlink/pull/4745)) - Fixed: ustreamtv plugin ([#4761](https://github.com/streamlink/streamlink/pull/4761)) - Fixed: huya plugin ([#4763](https://github.com/streamlink/streamlink/pull/4763)) - Fixed: atresplayer, mitele and rtve plugins ([#4759](https://github.com/streamlink/streamlink/pull/4759), [#4760](https://github.com/streamlink/streamlink/pull/4760), [#4766](https://github.com/streamlink/streamlink/pull/4766)) - Fixed: albavision, hiplayer and htv plugins ([#4770](https://github.com/streamlink/streamlink/pull/4770)) - Fixed: OKru plugin with support for the mobile page ([#4780](https://github.com/streamlink/streamlink/pull/4780)) - Fixed: trovo plugin VODs ([#4812](https://github.com/streamlink/streamlink/pull/4812)) - API: added `Streamlink` and `HTTPSession` typing informations to `Plugin` and `Stream` (including its various subclasses) ([#4802](https://github.com/streamlink/streamlink/pull/4802), [#4814](https://github.com/streamlink/streamlink/pull/4814)) - API: added `pluginargument` decorator ([#4747](https://github.com/streamlink/streamlink/pull/4747)) - Docs: updated `pluginmatcher` and `pluginargument` documentation ([#4771](https://github.com/streamlink/streamlink/pull/4771)) [Full changelog](https://github.com/streamlink/streamlink/compare/4.3.0...5.0.0) ## streamlink 4.3.0 (2022-08-15) Release highlights: - Improved: CLI download progress output ([#4656](https://github.com/streamlink/streamlink/pull/4656)) - Fixed: consecutive FFmpeg executable lookups not being cached ([#4660](https://github.com/streamlink/streamlink/pull/4660)) - Fixed: `--ffmpeg-verbose-path` not expanding `~` to the user's home directory ([#4688](https://github.com/streamlink/streamlink/pull/4688)) - Fixed: deprecated stdlib API calls in the upcoming Python 3.11 release ([#4651](https://github.com/streamlink/streamlink/pull/4651), [#4654](https://github.com/streamlink/streamlink/pull/4654)) - Fixed: huya plugin ([#4685](https://github.com/streamlink/streamlink/pull/4685)) - Fixed: livestream plugin ([#4679](https://github.com/streamlink/streamlink/pull/4679)) - Fixed: picarto plugin ([#4729](https://github.com/streamlink/streamlink/pull/4729)) - Fixed: nbcnews plugin ([#4668](https://github.com/streamlink/streamlink/pull/4668)) - Fixed: deutschewelle plugin ([#4725](https://github.com/streamlink/streamlink/pull/4725)) - Added plugins: atpchallenger ([#4700](https://github.com/streamlink/streamlink/pull/4700)) - Removed plugins: nbc + nbcsports + theplatform ([#4731](https://github.com/streamlink/streamlink/pull/4731)), common\_jwplayer ([#4733](https://github.com/streamlink/streamlink/pull/4733)) - Docs: various CLI related improvements ([#4659](https://github.com/streamlink/streamlink/pull/4659)) - Docs: removed OpenBSD and Ubuntu from install docs ([#4681](https://github.com/streamlink/streamlink/pull/4681)) - Plugin API: added new validation schemas and updated validators ([#4691](https://github.com/streamlink/streamlink/pull/4691), [#4709](https://github.com/streamlink/streamlink/pull/4709), [#4732](https://github.com/streamlink/streamlink/pull/4732)) [Full changelog](https://github.com/streamlink/streamlink/compare/4.2.0...4.3.0) ## streamlink 4.2.0 (2022-07-09) Release highlights: - Added: new Windows portable builds ([#4581](https://github.com/streamlink/streamlink/pull/4581)) - Added: more dependency versions to debug log header ([#4575](https://github.com/streamlink/streamlink/pull/4575)) - Added: parsed multivariant playlist reference to `HLSStream` and `MuxedHLSStream` ([#4568](https://github.com/streamlink/streamlink/pull/4568)) - Fixed: unnecessary delay when closing `DASHStream`s ([#4630](https://github.com/streamlink/streamlink/pull/4630)) - Fixed: `FFmpegMuxer` not closing sub-streams concurrently ([#4634](https://github.com/streamlink/streamlink/pull/4634)) - Fixed: threading issue when closing `WebsocketClient` connections ([#4608](https://github.com/streamlink/streamlink/pull/4608)) - Fixed: handling of `PluginError`s when outputting JSON data via `--json` ([#4590](https://github.com/streamlink/streamlink/pull/4590)) - Fixed: broken YouTube plugin when setting custom authentication headers ([#4576](https://github.com/streamlink/streamlink/pull/4576)) - Fixed: "source" Twitch VODs not being considered "best" ([#4625](https://github.com/streamlink/streamlink/pull/4625)) - Fixed: and rewritten FilmOn plugin ([#4573](https://github.com/streamlink/streamlink/pull/4573)) - Fixed: websocket issue in Twitcasting plugin ([#4608](https://github.com/streamlink/streamlink/pull/4608), [#4628](https://github.com/streamlink/streamlink/pull/4628)) - Fixed: VK plugin ([#4613](https://github.com/streamlink/streamlink/pull/4613), [#4638](https://github.com/streamlink/streamlink/pull/4638)) - Fixed: various other plugin issues (see full changelog) - New plugins: Aloula ([#4572](https://github.com/streamlink/streamlink/pull/4572)) - Removed plugins: Eltrecetv ([#4593](https://github.com/streamlink/streamlink/pull/4593)) - Docs: added openSUSE ([#4596](https://github.com/streamlink/streamlink/pull/4596)) and Scoop ([#4600](https://github.com/streamlink/streamlink/pull/4600)) packages - Docs: improved some links in CLI docs ([#4623](https://github.com/streamlink/streamlink/pull/4623)) - Docs: upgraded `furo` theme to `2022.06.04.1`, require `sphinx` `>=4`, and replace `recommonmark` with `myst-parser` ([#4610](https://github.com/streamlink/streamlink/pull/4610)) - Build: fixed outdated `python_requires` value in `setup.cfg` ([#4580](https://github.com/streamlink/streamlink/pull/4580)) - Build: upgraded `versioningit` build dependency to `>=2.0.0 <3` ([#4597](https://github.com/streamlink/streamlink/pull/4597)) [Full changelog](https://github.com/streamlink/streamlink/compare/4.1.0...4.2.0) ## streamlink 4.1.0 (2022-05-30) Release highlights: - Improved: decryption of HLS streams ([#4533](https://github.com/streamlink/streamlink/pull/4533)) - Improved: HLS playlist parsing ([#4540](https://github.com/streamlink/streamlink/pull/4540), [#4552](https://github.com/streamlink/streamlink/pull/4552)) - Improved: validation schemas and error handling/printing ([#4514](https://github.com/streamlink/streamlink/pull/4514)) - Improved: string representations of `Stream` implementations ([#4521](https://github.com/streamlink/streamlink/pull/4521)) - Fixed: new YouTube consent dialog ([#4515](https://github.com/streamlink/streamlink/pull/4515)) - Fixed: crunchyroll plugin ([#4510](https://github.com/streamlink/streamlink/pull/4510)) - Fixed: nicolive email logins ([#4553](https://github.com/streamlink/streamlink/pull/4553)) - Fixed: threading issue when closing segmented streams ([#4517](https://github.com/streamlink/streamlink/pull/4517)) - Removed: suppression of `InsecureRequestWarning` ([#4525](https://github.com/streamlink/streamlink/pull/4525)) - New plugins: blazetv ([#4548](https://github.com/streamlink/streamlink/pull/4548)), hiplayer ([#4507](https://github.com/streamlink/streamlink/pull/4507)), useetv ([#4536](https://github.com/streamlink/streamlink/pull/4536)) - Removed plugins: rotana ([#4512](https://github.com/streamlink/streamlink/pull/4512)) [Full changelog](https://github.com/streamlink/streamlink/compare/4.0.1...4.1.0) ## streamlink 4.0.1 (2022-05-01) No code changes. Please see the [changelog of the `4.0.0` release](https://streamlink.github.io/changelog.html#streamlink-4-0-0-2022-05-01), as it contains breaking changes. - Fixed: missing source-dist tarballs on GitHub release page ([#4503](https://github.com/streamlink/streamlink/pull/4503)) [Full changelog](https://github.com/streamlink/streamlink/compare/4.0.0...4.0.1) ## streamlink 4.0.0 (2022-05-01) Breaking changes: - BREAKING: dropped support for Python 3.6 ([#4442](https://github.com/streamlink/streamlink/pull/4442)) - BREAKING/API: removed [`streamlink.plugin.api.utils`](https://streamlink.github.io/deprecations.html#removal-of-streamlink-plugin-api-utils) module ([#4467](https://github.com/streamlink/streamlink/pull/4467)) - BREAKING/setup: switched to PEP 518 build system declaration and replaced versioneer in favor of versioningit ([#4440](https://github.com/streamlink/streamlink/pull/4440)) - BREAKING/packaging: replaced Windows installers with new ones built at [streamlink/windows-installer](https://github.com/streamlink/windows-installer) ([#4405](https://github.com/streamlink/streamlink/pull/4405)) - Added: new embedded Python builds for 3.8 and 3.10, both x86 and x86_64 - Updated: embedded FFmpeg to 5.0 Release highlights: - Added: support for `--record=-`, for writing data to stdout while watching at the same time ([#4462](https://github.com/streamlink/streamlink/pull/4462)) - Added: `plugin` variable for `--title`, `--output`, `--record` and `--record-and-pipe` ([#4437](https://github.com/streamlink/streamlink/pull/4437)) - Added: missing CLI protocol parameter support for DASH streams ([#4434](https://github.com/streamlink/streamlink/pull/4434)) - Updated: CLI and API documentation ([#4415](https://github.com/streamlink/streamlink/pull/4415), [#4424](https://github.com/streamlink/streamlink/pull/4424), [#4430](https://github.com/streamlink/streamlink/pull/4430)) - Updated: plugin description documentation ([#4391](https://github.com/streamlink/streamlink/pull/4391)) - Fixed: nicolive email logins ([#4380](https://github.com/streamlink/streamlink/pull/4380)) - Fixed: various other plugin issues (see the changelog down below) - New plugins: cmmedia ([#4416](https://github.com/streamlink/streamlink/pull/4416)), htv ([#4431](https://github.com/streamlink/streamlink/pull/4431)), mdstrm ([#4395](https://github.com/streamlink/streamlink/pull/4395)), trovo ([#4471](https://github.com/streamlink/streamlink/pull/4471)) - Removed plugins: abweb ([#4270](https://github.com/streamlink/streamlink/pull/4270)), garena ([#4460](https://github.com/streamlink/streamlink/pull/4460)), senategov ([#4458](https://github.com/streamlink/streamlink/pull/4458)), teamliquid ([#4393](https://github.com/streamlink/streamlink/pull/4393)), tlctr ([#4432](https://github.com/streamlink/streamlink/pull/4432)), vrtbe ([#4459](https://github.com/streamlink/streamlink/pull/4459)) [Full changelog](https://github.com/streamlink/streamlink/compare/3.2.0...4.0.0) ## streamlink 3.2.0 (2022-03-05) Release highlights: - Added: log message for the resolved path when writing output to file ([#4336](https://github.com/streamlink/streamlink/pull/4336)) - Added: new plugins for rtpa.es ([#4344](https://github.com/streamlink/streamlink/pull/4344)) and lnk.lt ([#4364](https://github.com/streamlink/streamlink/pull/4364)) - Changed: metadata requirements for built-in plugins ([#4374](https://github.com/streamlink/streamlink/pull/4374)) - Improved: plugins documentation ([#4374](https://github.com/streamlink/streamlink/pull/4374)) - Fixed: filmon plugin, requires at least OpenSSL 1.1.0 ([#4335](https://github.com/streamlink/streamlink/pull/4335), [#4345](https://github.com/streamlink/streamlink/pull/4345)) - Fixed: mildom plugin ([#4375](https://github.com/streamlink/streamlink/pull/4375)) - Fixed: nicolive email logins with confirmation codes ([#4380](https://github.com/streamlink/streamlink/pull/4380)) - Fixed: various other plugin issues, see the changelog down below - Upgraded: Windows installer's Python and dependency versions ([#4330](https://github.com/streamlink/streamlink/pull/4330), [#4347](https://github.com/streamlink/streamlink/pull/4347)) [Full changelog](https://github.com/streamlink/streamlink/compare/3.1.1...3.2.0) ## streamlink 3.1.1 (2022-01-25) Patch release: - Fixed: broken `streamlink.exe`/`streamlinkw.exe` executables in Windows installer ([#4308](https://github.com/streamlink/streamlink/pull/4308)) [Full changelog](https://github.com/streamlink/streamlink/compare/3.1.0...3.1.1) ## streamlink 3.1.0 (2022-01-22) Release highlights: - Changed: file overwrite prompt to wait for user input before opening streams ([#4252](https://github.com/streamlink/streamlink/pull/4252)) - Fixed: log messages appearing in `--json` output ([#4258](https://github.com/streamlink/streamlink/pull/4258)) - Fixed: keep-alive TCP connections when filtering out HLS segments ([#4229](https://github.com/streamlink/streamlink/pull/4229)) - Fixed: sort order of DASH streams with the same video resolution ([#4220](https://github.com/streamlink/streamlink/pull/4220)) - Fixed: HLS segment byterange offsets ([#4301](https://github.com/streamlink/streamlink/pull/4301), [#4302](https://github.com/streamlink/streamlink/pull/4302)) - Fixed: YouTube /live URLs ([#4222](https://github.com/streamlink/streamlink/pull/4222)) - Fixed: UStream websocket address ([#4238](https://github.com/streamlink/streamlink/pull/4238)) - Fixed: Pluto desync issues by filtering out bumper segments ([#4255](https://github.com/streamlink/streamlink/pull/4255)) - Fixed: various plugin issues - please see the changelog down below - Removed plugins: abweb ([#4270](https://github.com/streamlink/streamlink/pull/4270)), latina ([#4269](https://github.com/streamlink/streamlink/pull/4269)), live_russia_tv ([#4263](https://github.com/streamlink/streamlink/pull/4263)), liveme ([#4264](https://github.com/streamlink/streamlink/pull/4264)) [Full changelog](https://github.com/streamlink/streamlink/compare/3.0.3...3.1.0) ## streamlink 3.0.3 (2021-11-27) Patch release: - Fixed: broken output of the `--help` CLI argument ([#4213](https://github.com/streamlink/streamlink/pull/4213)) - Fixed: parsing of invalid HTML5 documents ([#4210](https://github.com/streamlink/streamlink/pull/4210)) Please see the [changelog of 3.0.0](https://streamlink.github.io/changelog.html#streamlink-3-0-0-2021-11-17), as it contains breaking changes that may require user interaction. [Full changelog](https://github.com/streamlink/streamlink/compare/3.0.2...3.0.3) ## streamlink 3.0.2 (2021-11-25) Patch release: - Added: support for the `id` plugin metadata property ([#4203](https://github.com/streamlink/streamlink/pull/4203)) - Updated: Twitch access token request parameter regarding embedded ads ([#4194](https://github.com/streamlink/streamlink/pull/4194)) - Fixed: early `SIGINT`/`SIGTERM` signal handling ([#4190](https://github.com/streamlink/streamlink/pull/4190)) - Fixed: broken character set decoding when parsing HTML documents ([#4201](https://github.com/streamlink/streamlink/pull/4201)) - Fixed: missing home directory expansion (tilde character) in file output paths ([#4204](https://github.com/streamlink/streamlink/pull/4204)) - New plugin: tviplayer ([#4199](https://github.com/streamlink/streamlink/pull/4199)) [Full changelog](https://github.com/streamlink/streamlink/compare/3.0.1...3.0.2) ## streamlink 3.0.1 (2021-11-17) Patch release: - Fixed: broken pycountry import in Windows installer's Python environment ([#4180](https://github.com/streamlink/streamlink/pull/4180)) [Full changelog](https://github.com/streamlink/streamlink/compare/3.0.0...3.0.1) ## streamlink 3.0.0 (2021-11-17) Breaking changes: - BREAKING: dropped support for RTMP, HDS and AkamaiHD streams ([#4169](https://github.com/streamlink/streamlink/pull/4169), [#4168](https://github.com/streamlink/streamlink/pull/4168)) - removed the `rtmp://`, `hds://` and `akamaihd://` protocol plugins - removed all Flash related code - upgraded all plugins using these old streaming protocols - dropped RTMPDump dependency - BREAKING: removed the following CLI arguments (and respective session options): ([#4169](https://github.com/streamlink/streamlink/pull/4169), [#4168](https://github.com/streamlink/streamlink/pull/4168)) - `--rtmp-rtmpdump`, `--rtmpdump`, `--rtmp-proxy`, `--rtmp-timeout` Users of Streamlink's Windows installer will need to update their [config file](https://streamlink.github.io/cli.html#configuration-file). - `--subprocess-cmdline`, `--subprocess-errorlog`, `--subprocess-errorlog-path` - `--hds-live-edge`, `--hds-segment-attempts`, `--hds-segment-threads`, `--hds-segment-timeout`, `--hds-timeout` - BREAKING: switched from HTTP to HTTPS for all kinds of scheme-less input URLs. If a site or http-proxy doesn't support HTTPS, then HTTP needs to be set explicitly. ([#4068](https://github.com/streamlink/streamlink/pull/4068), [#4053](https://github.com/streamlink/streamlink/pull/4053)) - BREAKING/API: changed `Session.resolve_url()` and `Session.resolve_url_no_redirect()` to return a tuple of a plugin class and the resolved URL instead of an initialized plugin class instance. This fixes the availability of plugin options in a plugin's constructor. ([#4163](https://github.com/streamlink/streamlink/pull/4163)) - BREAKING/requirements: dropped alternative dependency `pycrypto` and removed the `STREAMLINK_USE_PYCRYPTO` env var switch ([#4174](https://github.com/streamlink/streamlink/pull/4174)) - BREAKING/requirements: switched from `iso-639`+`iso3166` to `pycountry` and removed the `STREAMLINK_USE_PYCOUNTRY` env var switch ([#4175](https://github.com/streamlink/streamlink/pull/4175)) - BREAKING/setup: disabled unsupported Python versions, disabled the deprecated `test` setuptools command, removed the `NO_DEPS` env var, and switched to declarative package data via `setup.cfg` ([#4079](https://github.com/streamlink/streamlink/pull/4079), [#4107](https://github.com/streamlink/streamlink/pull/4107), [#4115](https://github.com/streamlink/streamlink/pull/4115), [#4113](https://github.com/streamlink/streamlink/pull/4113)) Release highlights: - Deprecated: `--https-proxy` in favor of a single `--http-proxy` CLI argument (and respective session option). Both now set the same proxy for all HTTPS/HTTP requests and websocket connections. [`--https-proxy` will be removed in a future release.](https://streamlink.github.io/deprecations.html#streamlink-3-0-0) ([#4120](https://github.com/streamlink/streamlink/pull/4120)) - Added: official support for Python 3.10 ([#4144](https://github.com/streamlink/streamlink/pull/4144)) - Added: `--twitch-api-header` for only setting Twitch.tv API requests headers (for authentication, etc.) as an alternative to `--http-header` ([#4156](https://github.com/streamlink/streamlink/pull/4156)) - Added: BASH and ZSH completions to sdist tarball and wheels. ([#4048](https://github.com/streamlink/streamlink/pull/4048), [#4178](https://github.com/streamlink/streamlink/pull/4178)) - Added: support for creating parent directories via metadata variables in file output paths ([#4085](https://github.com/streamlink/streamlink/pull/4085)) - Added: new WebsocketClient implementation ([#4153](https://github.com/streamlink/streamlink/pull/4153)) - Updated: plugins using websocket connections - nicolive, ustreamtv, twitcasting ([#4155](https://github.com/streamlink/streamlink/pull/4155), [#4164](https://github.com/streamlink/streamlink/pull/4164), [#4154](https://github.com/streamlink/streamlink/pull/4154)) - Updated: circumvention for YouTube's age verification ([#4058](https://github.com/streamlink/streamlink/pull/4058)) - Updated: and fixed lots of other plugins, see the detailed changelog below - Reverted: HLS segment downloads always being streamed, and added back `--hls-segment-stream-data` to prevent connection issues ([#4159](https://github.com/streamlink/streamlink/pull/4159)) - Fixed: URL percent-encoding for sites which require the lowercase format ([#4003](https://github.com/streamlink/streamlink/pull/4003)) - Fixed: XML parsing issues ([#4075](https://github.com/streamlink/streamlink/pull/4075)) - Fixed: broken `method` parameter when using the `httpstream://` protocol plugin ([#4171](https://github.com/streamlink/streamlink/pull/4171)) - Fixed: test failures when the `brotli` package is installed ([#4022](https://github.com/streamlink/streamlink/pull/4022)) - Requirements: bumped `lxml` to `>4.6.4,<5.0` and `websocket-client` to `>=1.2.1,<2.0` ([#4143](https://github.com/streamlink/streamlink/pull/4143), [#4153](https://github.com/streamlink/streamlink/pull/4153)) - Windows installer: upgraded Python to `3.9.8` and FFmpeg to `n4.4.1` ([#4176](https://github.com/streamlink/streamlink/pull/4176), [#4124](https://github.com/streamlink/streamlink/pull/4124)) - Documentation: upgraded to first stable version of the Furo theme ([#4000](https://github.com/streamlink/streamlink/pull/4000)) - New plugins: pandalive ([#4064](https://github.com/streamlink/streamlink/pull/4064)) - Removed plugins: tga ([#4129](https://github.com/streamlink/streamlink/pull/4129)), viasat ([#4087](https://github.com/streamlink/streamlink/pull/4087)), viutv ([#4018](https://github.com/streamlink/streamlink/pull/4018)), webcast_india_gov ([#4024](https://github.com/streamlink/streamlink/pull/4024)) [Full changelog](https://github.com/streamlink/streamlink/compare/2.4.0...3.0.0) ## streamlink 2.4.0 (2021-09-07) Release highlights: - Deprecated: stream-type specific stream transport options in favor of generic options ([#3893](https://github.com/streamlink/streamlink/pull/3893)) - use `--stream-segment-attempts` instead of `--{dash,hds,hls}-segment-attempts` - use `--stream-segment-threads` instead of `--{dash,hds,hls}-segment-threads` - use `--stream-segment-timeout` instead of `--{dash,hds,hls}-segment-timeout` - use `--stream-timeout` instead of `--{dash,hds,hls,rtmp,http-stream}-timeout` See the documentation's [deprecations page](https://streamlink.github.io/latest/deprecations.html#streamlink-2-4-0) for more information. - Deprecated: `--hls-segment-stream-data` option and made it always stream segment data ([#3894](https://github.com/streamlink/streamlink/pull/3894)) - Updated: Python version of the Windows installer from 3.8 to 3.9 and dropped support for Windows 7 due to Python incompatibilities ([#3918](https://github.com/streamlink/streamlink/pull/3918)) See the documentation's [install page](https://streamlink.github.io/install.html) for alternative installation methods on Windows 7. - Updated: FFmpeg in the Windows Installer from 4.2 (Zeranoe) to 4.4 ([streamlink/FFmpeg-Builds](https://github.com/streamlink/FFmpeg-Builds)) ([#3981](https://github.com/streamlink/streamlink/pull/3981)) - Added: `{author}`, `{category}`/`{game}`, `{title}` and `{url}` variables to `--output`, `--record` and `--record-and-play` ([#3962](https://github.com/streamlink/streamlink/pull/3962)) - Added: `{time}`/`{time:custom-format}` variable to `--title`, `--output`, `--record` and `--record-and-play` ([#3993](https://github.com/streamlink/streamlink/pull/3993)) - Added: `--fs-safe-rules` for changing character replacement rules in file outputs ([#3962](https://github.com/streamlink/streamlink/pull/3962)) - Added: plugin metadata to `--json` stream data output ([#3987](https://github.com/streamlink/streamlink/pull/3987)) - Fixed: named pipes not being cleaned up by FFMPEGMuxer ([#3992](https://github.com/streamlink/streamlink/pull/3992)) - Fixed: KeyError on invalid variables in `--player-args` ([#3988](https://github.com/streamlink/streamlink/pull/3988)) - Fixed: tests failing in certain cases when run in different order ([#3920](https://github.com/streamlink/streamlink/pull/3920)) - Fixed: initial HLS playlist parsing issues ([#3903](https://github.com/streamlink/streamlink/pull/3903), [#3910](https://github.com/streamlink/streamlink/pull/3910)) - Fixed: various plugin issues. Please see the changelog down below. - Dependencies: added `lxml>=4.6.3` ([#3952](https://github.com/streamlink/streamlink/pull/3952)) - Dependencies: switched back to `requests>=2.26.0` on Windows ([#3930](https://github.com/streamlink/streamlink/pull/3930)) - Removed plugins: animeworld ([#3951](https://github.com/streamlink/streamlink/pull/3951)), gardenersworld ([#3966](https://github.com/streamlink/streamlink/pull/3966)), huomao ([#3932](https://github.com/streamlink/streamlink/pull/3932)) [Full changelog](https://github.com/streamlink/streamlink/compare/2.3.0...2.4.0) ## streamlink 2.3.0 (2021-07-26) Release highlights: - Implemented: new plugin URL matching API ([#3814](https://github.com/streamlink/streamlink/issues/3814), [#3821](https://github.com/streamlink/streamlink/pull/3821)) Third-party plugins which use the old API will still be resolved, but those plugins will have to upgrade in the future. See the documentation's [deprecations page](https://streamlink.github.io/latest/deprecations.html#streamlink-2-3-0) for more information. - Implemented: HLS media initialization section (fragmented MPEG-4 streams) ([#3828](https://github.com/streamlink/streamlink/pull/3828)) - Upgraded: `requests` to `>=2.26.0,<3` and set it to `==2.25.1` on Windows ([#3864](https://github.com/streamlink/streamlink/pull/3864), [#3880](https://github.com/streamlink/streamlink/pull/3880)) - Fixed: YouTube channel URLs, premiering live streams, added API fallback ([#3847](https://github.com/streamlink/streamlink/pull/3847), [#3873](https://github.com/streamlink/streamlink/pull/3873), [#3809](https://github.com/streamlink/streamlink/pull/3809)) - Removed plugins: canalplus ([#3841](https://github.com/streamlink/streamlink/pull/3841)), dommune ([#3818](https://github.com/streamlink/streamlink/pull/3818)), liveedu ([#3845](https://github.com/streamlink/streamlink/pull/3845)), periscope ([#3813](https://github.com/streamlink/streamlink/pull/3813)), powerapp ([#3816](https://github.com/streamlink/streamlink/pull/3816)), rtlxl ([#3842](https://github.com/streamlink/streamlink/pull/3842)), streamingvideoprovider ([#3843](https://github.com/streamlink/streamlink/pull/3843)), teleclubzoom ([#3817](https://github.com/streamlink/streamlink/pull/3817)), tigerdile ([#3819](https://github.com/streamlink/streamlink/pull/3819)) [Full changelog](https://github.com/streamlink/streamlink/compare/2.2.0...2.3.0) ## streamlink 2.2.0 (2021-06-19) Release highlights: - Changed: default config file path on macOS and Windows ([#3766](https://github.com/streamlink/streamlink/pull/3766)) - macOS: `${HOME}/Library/Application Support/streamlink/config` - Windows: `%APPDATA%\streamlink\config` - Changed: default custom plugins directory path on macOS and Linux/BSD ([#3766](https://github.com/streamlink/streamlink/pull/3766)) - macOS: `${HOME}/Library/Application Support/streamlink/plugins` - Linux/BSD: `${XDG_DATA_HOME:-${HOME}/.local/share}/streamlink/plugins` - Deprecated: old config file paths and old custom plugins directory paths ([#3784](https://github.com/streamlink/streamlink/pull/3784)) - Windows: - `%APPDATA%\streamlink\streamlinkrc` - macOS: - `${XDG_CONFIG_HOME:-${HOME}/.config}/streamlink/config` - `${XDG_CONFIG_HOME:-${HOME}/.config}/streamlink/plugins` - `${HOME}/.streamlinkrc` - Linux/BSD: - `${XDG_CONFIG_HOME:-${HOME}/.config}/streamlink/plugins` - `${HOME}/.streamlinkrc` Support for these old paths will be dropped in the future. See the [CLI documentation](https://streamlink.github.io/cli.html) for all the details regarding these changes. - Implemented: `--logfile` CLI argument ([#3753](https://github.com/streamlink/streamlink/pull/3753)) - Fixed: Youtube 404 errors by dropping private API calls (plugin rewrite) ([#3797](https://github.com/streamlink/streamlink/pull/3797)) - Fixed: Twitch clips ([#3762](https://github.com/streamlink/streamlink/pull/3762), [#3775](https://github.com/streamlink/streamlink/pull/3775)) and hosted channel redirection ([#3776](https://github.com/streamlink/streamlink/pull/3776)) - Fixed: Olympicchannel plugin ([#3760](https://github.com/streamlink/streamlink/pull/3760)) - Fixed: various Zattoo plugin issues ([#3773](https://github.com/streamlink/streamlink/pull/3773), [#3780](https://github.com/streamlink/streamlink/pull/3780)) - Fixed: HTTP responses with truncated body and mismatching content-length header ([#3768](https://github.com/streamlink/streamlink/pull/3768)) - Fixed: scheme-less URLs with address:port for `--http-proxy`, etc. ([#3765](https://github.com/streamlink/streamlink/pull/3765)) - Fixed: rendered man page path on Sphinx 4 ([#3750](https://github.com/streamlink/streamlink/pull/3750)) - Added plugins: mildom.com ([#3584](https://github.com/streamlink/streamlink/pull/3584)), booyah.live ([#3585](https://github.com/streamlink/streamlink/pull/3585)), mediavitrina.ru ([#3743](https://github.com/streamlink/streamlink/pull/3743)) - Removed plugins: ine.com ([#3781](https://github.com/streamlink/streamlink/pull/3781)), playtv.fr ([#3798](https://github.com/streamlink/streamlink/pull/3798)) [Full changelog](https://github.com/streamlink/streamlink/compare/2.1.2...2.2.0) ## streamlink 2.1.2 (2021-05-20) Patch release: - Fixed: youtube 404 errors ([#3732](https://github.com/streamlink/streamlink/pull/3732)), consent dialog ([#3672](https://github.com/streamlink/streamlink/pull/3672)) and added short URLs ([#3677](https://github.com/streamlink/streamlink/pull/3677)) - Fixed: picarto plugin ([#3661](https://github.com/streamlink/streamlink/pull/3661)) - Fixed: euronews plugin ([#3698](https://github.com/streamlink/streamlink/pull/3698)) - Fixed: bbciplayer plugin ([#3725](https://github.com/streamlink/streamlink/pull/3725)) - Fixed: missing removed-plugins-file in `setup.py build` ([#3653](https://github.com/streamlink/streamlink/pull/3653)) - Changed: HLS streams to use rounded bandwidth names ([#3721](https://github.com/streamlink/streamlink/pull/3721)) - Removed: plugin for hitbox.tv / smashcast.tv ([#3686](https://github.com/streamlink/streamlink/pull/3686)), tvplayer.com ([#3673](https://github.com/streamlink/streamlink/pull/3673)) [Full changelog](https://github.com/streamlink/streamlink/compare/2.1.1...2.1.2) ## streamlink 2.1.1 (2021-03-25) Patch release: - Fixed: test failure due to missing removed plugins file in sdist tarball ([#3644](https://github.com/streamlink/streamlink/pull/3644)). [Full changelog](https://github.com/streamlink/streamlink/compare/2.1.0...2.1.1) ## streamlink 2.1.0 (2021-03-22) Release highlights: - Added: `--interface`, `-4` / `--ipv4` and `-6` / `--ipv6` ([#3483](https://github.com/streamlink/streamlink/pull/3483)) - Added: `--niconico-purge-credentials` ([#3434](https://github.com/streamlink/streamlink/pull/3434)) - Added: `--twitcasting-password` ([#3505](https://github.com/streamlink/streamlink/pull/3505)) - Added: Linux AppImages ([#3611](https://github.com/streamlink/streamlink/pull/3611)) - Added: pre-built man page to bdist wheels and sdist tarballs ([#3459](https://github.com/streamlink/streamlink/pull/3459), [#3510](https://github.com/streamlink/streamlink/pull/3510)) - Added: plugin for ahaber.com.tr and atv.com.tr ([#3484](https://github.com/streamlink/streamlink/pull/3484)), nimo.tv ([#3508](https://github.com/streamlink/streamlink/pull/3508)) - Fixed: `--player-http` / `--player-continuous-http` HTTP server being bound to all interfaces ([#3450](https://github.com/streamlink/streamlink/pull/3450)) - Fixed: handling of languages without alpha_2 code when using pycountry ([#3518](https://github.com/streamlink/streamlink/pull/3518)) - Fixed: memory leak when calling `streamlink.streams()` ([#3486](https://github.com/streamlink/streamlink/pull/3486)) - Fixed: race condition in HLS related tests ([#3454](https://github.com/streamlink/streamlink/pull/3454)) - Fixed: `--player-fifo` issues on Windows with VLC or MPV ([#3619](https://github.com/streamlink/streamlink/pull/3619)) - Fixed: various plugins issues (see detailed changelog down below) - Removed: Windows portable (RosadinTV) ([#3535](https://github.com/streamlink/streamlink/pull/3535)) - Removed: plugin for micous.com ([#3457](https://github.com/streamlink/streamlink/pull/3457)), ntvspor.net ([#3485](https://github.com/streamlink/streamlink/pull/3485)), btsports ([#3636](https://github.com/streamlink/streamlink/pull/3636)) - Dependencies: set `websocket-client` to `>=0.58.0` ([#3634](https://github.com/streamlink/streamlink/pull/3634)) [Full changelog](https://github.com/streamlink/streamlink/compare/2.0.0...2.1.0) ## streamlink 2.0.0 (2020-12-22) Release highlights: - BREAKING: dropped support for Python 2 and Python 3.5 ([#3232](https://github.com/streamlink/streamlink/pull/3232), [#3269](https://github.com/streamlink/streamlink/pull/3269)) - BREAKING: updated the Python version of the Windows installer to 3.8 ([#3330](https://github.com/streamlink/streamlink/pull/3330)) Users of Windows 7 will need their system to be fully upgraded. - BREAKING: removed all deprecated CLI arguments ([#3277](https://github.com/streamlink/streamlink/pull/3277), [#3349](https://github.com/streamlink/streamlink/pull/3349)) - `--http-cookies`, `--http-headers`, `--http-query-params` - `--no-version-check` - `--rtmpdump-proxy` - `--cmdline`, `-c` - `--errorlog`, `-e` - `--errorlog-path` - `--btv-username`, `--btv-password` - `--crunchyroll-locale` - `--pixiv-username`, `--pixiv-password` - `--twitch-oauth-authenticate`, `--twitch-oauth-token`, `--twitch-cookie` - `--ustvnow-station-code` - `--youtube-api-key` - BREAKING: replaced various subtitle muxing CLI arguments with `--mux-subtitles` ([#3324](https://github.com/streamlink/streamlink/pull/3324)) - `--funimationnow-mux-subtitles` - `--pluzz-mux-subtitles` - `--rtve-mux-subtitles` - `--svtplay-mux-subtitles` - `--vimeo-mux-subtitles` - BREAKING: sideloading faulty plugins will now raise an `Exception` ([#3366](https://github.com/streamlink/streamlink/pull/3366)) - BREAKING: changed trace logging timestamp format ([#3273](https://github.com/streamlink/streamlink/pull/3273)) - BREAKING/API: removed deprecated `Session` compat options ([#3349](https://github.com/streamlink/streamlink/pull/3349)) - BREAKING/API: removed deprecated custom `Logger` and `LogRecord` ([#3273](https://github.com/streamlink/streamlink/pull/3273)) - BREAKING/API: removed deprecated parameters from `HLSStream.parse_variant_playlist` ([#3347](https://github.com/streamlink/streamlink/pull/3347)) - BREAKING/API: removed `plugin.api.support_plugin` ([#3398](https://github.com/streamlink/streamlink/pull/3398)) - Added: new plugin for pluto.tv ([#3363](https://github.com/streamlink/streamlink/pull/3363)) - Added: support for HLS master playlist URLs to `--stream-url` / `--json` ([#3300](https://github.com/streamlink/streamlink/pull/3300)) - Added: `--ffmpeg-fout` for changing the output format of muxed streams ([#2892](https://github.com/streamlink/streamlink/pull/2892)) - Added: `--ffmpeg-copyts` and `--ffmpeg-start-at-zero` ([#3404](https://github.com/streamlink/streamlink/pull/3404), [#3413](https://github.com/streamlink/streamlink/pull/3413)) - Added: `--streann-url` for iframe referencing ([#3356](https://github.com/streamlink/streamlink/pull/3356)) - Added: `--niconico-timeshift-offset` ([#3425](https://github.com/streamlink/streamlink/pull/3425)) - Fixed: duplicate stream names in DASH inputs ([#3410](https://github.com/streamlink/streamlink/pull/3410)) - Fixed: youtube live playback ([#3268](https://github.com/streamlink/streamlink/pull/3268), [#3372](https://github.com/streamlink/streamlink/pull/3372), [#3428](https://github.com/streamlink/streamlink/pull/3428)) - Fixed: `--twitch-disable-reruns` ([#3375](https://github.com/streamlink/streamlink/pull/3375)) - Fixed: various plugins issues (see detailed changelog down below) - Changed: `{filename}` variable in `--player-args` / `-a` to `{playerinput}` and made both optional ([#3313](https://github.com/streamlink/streamlink/pull/3313)) - Changed: and fixed `streamlinkrc` config file in the Windows installer ([#3350](https://github.com/streamlink/streamlink/pull/3350)) - Changed: MPV's automated `--title` argument to `--force-media-title` ([#3405](https://github.com/streamlink/streamlink/pull/3405)) - Changed: HTML documentation theme to [furo](https://github.com/pradyunsg/furo) ([#3335](https://github.com/streamlink/streamlink/pull/3335)) - Removed: plugins for `skai`, `kingkong`, `ellobo`, `trt`/`trtspor`, `tamago`, `streamme`, `metube`, `cubetv`, `willax` [Full changelog](https://github.com/streamlink/streamlink/compare/1.7.0...2.0.0) ## streamlink 1.7.0 (2020-10-18) Release highlights: - Added: new plugins for micous.com, tv999.bg and cbsnews.com - Added: new embedded ad detection for Twitch streams ([#3213](https://github.com/streamlink/streamlink/pull/3213)) - Fixed: a few broken plugins and minor plugin issues (see changelog down below) - Fixed: arguments in config files were read too late before taking effect ([#3255](https://github.com/streamlink/streamlink/pull/3255)) - Fixed: Arte plugin returning too many streams and overriding primary ones ([#3228](https://github.com/streamlink/streamlink/pull/3228)) - Fixed: Twitch plugin error when stream metadata API response is empty ([#3223](https://github.com/streamlink/streamlink/pull/3223)) - Fixed: Zattoo login issues ([#3202](https://github.com/streamlink/streamlink/pull/3202)) - Changed: plugin request and submission guidelines ([#3244](https://github.com/streamlink/streamlink/pull/3244)) - Changed: refactored and cleaned up Twitch plugin ([#3227](https://github.com/streamlink/streamlink/pull/3227)) - Removed: `platform=_` stream token request parameter from Twitch plugin (again) ([#3220](https://github.com/streamlink/streamlink/pull/3220)) - Removed: plugins for itvplayer, aljazeeraen, srgssr and dingittv [Full changelog](https://github.com/streamlink/streamlink/compare/1.6.0...1.7.0) ## streamlink 1.6.0 (2020-09-22) Release highlights: - Fixed: lots of broken plugins and minor plugin issues (see changelog down below) - Fixed: embedded ads on Twitch with an ads workaround, removing pre-roll and mid-stream ads ([#3173](https://github.com/streamlink/streamlink/pull/3173)) - Fixed: read timeout error when filtering out HLS segments ([#3187](https://github.com/streamlink/streamlink/pull/3187)) - Fixed: twitch plugin logging incorrect low-latency status when pre-roll ads exist ([#3169](https://github.com/streamlink/streamlink/pull/3169)) - Fixed: crunchyroll auth logic ([#3150](https://github.com/streamlink/streamlink/pull/3150)) - Added: the `--hls-playlist-reload-time` parameter for customizing HLS playlist reload times ([#2925](https://github.com/streamlink/streamlink/pull/2925)) - Added: `python -m streamlink` invocation style support ([#3174](https://github.com/streamlink/streamlink/pull/3174)) - Added: plugin for mrt.com.mk ([#3097](https://github.com/streamlink/streamlink/pull/3097)) - Changed: yupptv plugin and replaced email+pass with id+token authentication ([#3116](https://github.com/streamlink/streamlink/pull/3116)) - Removed: plugins for vaughnlive, pandatv, douyutv, cybergame, europaplus and startv [Full changelog](https://github.com/streamlink/streamlink/compare/1.5.0...1.6.0) ## streamlink 1.5.0 (2020-07-07) A minor release with fixes for `pycountry==20.7.3` ([#3057](https://github.com/streamlink/streamlink/pull/3057)) and a few plugin additions and removals. And of course the usual plugin fixes and upgrades, which you can see in the git shortlog down below. Thank you to everyone involved! Support for Python2 has not been dropped yet (contrary to the comment in the last changelog), but will be in the near future. [Full changelog](https://github.com/streamlink/streamlink/compare/1.4.1...1.5.0) ## streamlink 1.4.1 (2020-04-24) No code changes. [See the full `1.4.0` changelog here.](https://github.com/streamlink/streamlink/releases/tag/1.4.0) [Full changelog](https://github.com/streamlink/streamlink/compare/1.4.0...1.4.1) ## streamlink 1.4.0 (2020-04-22) This will be the last release with support for Python 2, as it has finally reached its EOL at the beginning of this year. Streamlink 1.4.0 comes with lots of plugin fixes/improvements, as well as some new features and plugins, and also a few plugin removals. Notable changes: - New: low latency streaming on Twitch via `--twitch-low-latency` ([#2513](https://github.com/streamlink/streamlink/pull/2513)) - New: output HLS segment data immediately via `--hls-segment-stream-data` ([#2513](https://github.com/streamlink/streamlink/pull/2513)) - New: always show download progress via `--force-progress` ([#2438](https://github.com/streamlink/streamlink/pull/2438)) - New: URL template support for `--hls-segment-key-uri` ([#2821](https://github.com/streamlink/streamlink/pull/2821)) - Removed: Twitch auth logic, `--twitch-oauth-token`, `--twitch-oauth-authenticate`, `--twitch-cookie` ([#2846](https://github.com/streamlink/streamlink/pull/2846)) - Fixed: Youtube plugin ([#2858](https://github.com/streamlink/streamlink/pull/2858)) - Fixed: Crunchyroll plugin ([#2788](https://github.com/streamlink/streamlink/pull/2788)) - Fixed: Pixiv plugin ([#2840](https://github.com/streamlink/streamlink/pull/2840)) - Fixed: TVplayer plugin ([#2802](https://github.com/streamlink/streamlink/pull/2802)) - Fixed: Zattoo plugin ([#2887](https://github.com/streamlink/streamlink/pull/2887)) - Changed: set Firefox User-Agent HTTP header by default ([#2795](https://github.com/streamlink/streamlink/pull/2795)) - Changed: upgraded bundled FFmpeg to `4.2.2` in Windows installer ([#2916](https://github.com/streamlink/streamlink/pull/2916)) [Full changelog](https://github.com/streamlink/streamlink/compare/1.3.1...1.4.0) ## streamlink 1.3.1 (2020-01-27) A small patch release that addresses the removal of [MPV's legacy option syntax](https://mpv.io/manual/master/#legacy-option-syntax), also with fixes of several plugins, the addition of the `--twitch-disable-reruns` parameter and dropped support for Python 3.4. [Full changelog](https://github.com/streamlink/streamlink/compare/1.3.0...1.3.1) ## streamlink 1.3.0 (2019-11-22) A new release with plugin updates and fixes, including Twitch.tv (see [#2680](https://github.com/streamlink/streamlink/issues/2680)), which had to be delayed due to back and forth API changes. The Twitch.tv workarounds mentioned in [#2680](https://github.com/streamlink/streamlink/issues/2680) don't have to be applied anymore, but authenticating via `--twitch-oauth-token` has been disabled, regardless of the origin of the OAuth token (via `--twitch-oauth-authenticate` or the Twitch website). In order to not introduce breaking changes, both parameters have been kept in this release and the user name will still be logged when using an OAuth token, but receiving item drops or accessing restricted streams is not possible anymore. Plugins for the following sites have also been added: - albavision - news.now.com - twitcasting.tv - viu.tv - vlive.tv - willax.tv [Full changelog](https://github.com/streamlink/streamlink/compare/1.2.0...1.3.0) ## streamlink 1.2.0 (2019-08-18) Here are the changes for this month's release - Multiple plugin fixes - Fixed single hyphen params at the beginning of --player-args (#2333) - `--http-proxy` will set the default value of `--https-proxy` to same as `--http-proxy`. (#2536) - DASH Streams will handle headers correctly (#2545) - the timestamp for FFMPEGMuxer streams will start with zero (#2559) [Full changelog](https://github.com/streamlink/streamlink/compare/1.1.1...1.2.0) ## streamlink 1.1.1 (2019-04-02) This is just a small patch release which fixes a build/deploy issue with the new special wheels for Windows on PyPI. (#2392) [Please see the full changelog of the `1.1.0` release!](https://github.com/streamlink/streamlink/releases/tag/1.1.0) [Full changelog](https://github.com/streamlink/streamlink/compare/1.1.0...1.1.1) ## streamlink 1.1.0 (2019-03-31) These are the highlights of Streamlink's first minor release after the 1.0.0 milestone: - several plugin fixes, improvements and new plugin implementations - addition of the `--twitch-disable-ads` parameter for filtering out advertisement segments from Twitch.tv streams (#2372) - DASH stream improvements (#2285) - documentation enhancements (#2292, #2293) - addition of the `{url}` player title variable (#2232) - default player title config for PotPlayer (#2224) - new `streamlinkw` executable on Windows (wheels + installer) (#2326) - Github release assets simplification (#2360) [Full changelog](https://github.com/streamlink/streamlink/compare/1.0.0...1.1.0) ## streamlink 1.0.0 (2019-01-30) The celebratory release of Streamlink 1.0.0! *A lot* of hard work has gone into getting Streamlink to where it is. Not only is Streamlink used across multiple applications and platforms, but companies as well. Streamlink started from the inaugural [fork of Livestreamer](https://github.com/chrippa/livestreamer/issues/1427) on September 17th, 2016. Since then, We've hit multiple milestones: - Over 886 PRs - Hit 3,000 commits in Streamlink - Obtaining our first sponsors as well as backers of the project - The creation of our own logo (https://github.com/streamlink/streamlink/issues/1123) Thanks to everyone who has contributed to Streamlink (and our backers)! Without you, we wouldn't be where we are today. **Without further ado, here are the changes in release 1.0.0:** - We have a new icon / logo for Streamlink! (https://github.com/streamlink/streamlink/pull/2165) - Updated dependencies (https://github.com/streamlink/streamlink/pull/2230) - A *ton* of plugin updates. Have a look at [this search query](https://github.com/streamlink/streamlink/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Aclosed+plugins.+) for all the recent updates. - You can now provide a custom key URI to override HLS streams (https://github.com/streamlink/streamlink/pull/2139). For example: `--hls-segment-key-uri ` - User agents for API communication have been updated (https://github.com/streamlink/streamlink/pull/2194) - Special synonyms have been added to sort "best" and "worst" streams (https://github.com/streamlink/streamlink/pull/2127). For example: `streamlink --stream-sorting-excludes '>=480p' URL best,best-unfiltered` - Process output will no longer show if tty is unavailable (https://github.com/streamlink/streamlink/pull/2090) - We've removed BountySource in favour of our OpenCollective page. If you have any features you'd like to request, please open up an issue with the request and possibly consider backing us! - Improved terminal progress display for wide characters (https://github.com/streamlink/streamlink/pull/2032) - Fixed a bug with dynamic playlists on playback (https://github.com/streamlink/streamlink/pull/2096) - Fixed makeinstaller.sh (https://github.com/streamlink/streamlink/pull/2098) - Old Livestreamer deprecations and API references were removed (https://github.com/streamlink/streamlink/pull/1987) - Dependencies have been updated for Python (https://github.com/streamlink/streamlink/pull/1975) - Newer and more common User-Agents are now used (https://github.com/streamlink/streamlink/pull/1974) - DASH stream bitrates now round-up to the nearest 10, 100, 1000, etc. (https://github.com/streamlink/streamlink/pull/1995) - Updated documentation on issue templates (https://github.com/streamlink/streamlink/pull/1996) - URL have been added for better processing of HTML tags (https://github.com/streamlink/streamlink/pull/1675) - Fixed sort and prog issue (https://github.com/streamlink/streamlink/pull/1964) - Reformatted issue templates (https://github.com/streamlink/streamlink/pull/1966) - Fixed crashing bug with player-continuous-http option (https://github.com/streamlink/streamlink/pull/2234) - Make sure all dev dependencies (https://github.com/streamlink/streamlink/pull/2235) - -r parameter has been replaced for --rtmp-rtmpdump (https://github.com/streamlink/streamlink/pull/2152) **Breaking changes:** - A large number of unmaintained or NSFW plugins have been removed. You can find the PR that implemented that change here: https://github.com/streamlink/streamlink/pull/2003 . See our [CONTRIBUTING.md](https://github.com/streamlink/streamlink/blob/130489c6f5ad15488cd4ff7a25c74bf070f163ec/CONTRIBUTING.md) documentation for plugin policy. [Full changelog](https://github.com/streamlink/streamlink/compare/0.14.2...1.0.0) ## streamlink 0.14.2 (2018-06-28) Just a few small fixes in this release. - Fixed Twitch OAuth request flow (https://github.com/streamlink/streamlink/pull/1856) - Fix the tv3cat and vk plugins (https://github.com/streamlink/streamlink/pull/1851, https://github.com/streamlink/streamlink/pull/1874) - VOD supported added to atresplayer plugin (https://github.com/streamlink/streamlink/pull/1852, https://github.com/streamlink/streamlink/pull/1853) - Removed tv8cati and nineanime plugins (https://github.com/streamlink/streamlink/pull/1860, https://github.com/streamlink/streamlink/pull/1863) - Added mjunoon.tv plugin (https://github.com/streamlink/streamlink/pull/1857) [Full changelog](https://github.com/streamlink/streamlink/compare/0.14.0...0.14.2) ## streamlink 0.14.0 (2018-06-26) Here are the changes to this months release! - Multiple plugin fixes - Bug fixes for DASH streams (https://github.com/streamlink/streamlink/pull/1846) - Updated API call for api.utils hours_minutes_seconds (https://github.com/streamlink/streamlink/pull/1804) - Updated documentation (https://github.com/streamlink/streamlink/pull/1826) - Dict structures fix (https://github.com/streamlink/streamlink/pull/1792) - Reformated help menu (https://github.com/streamlink/streamlink/pull/1754) - Logger fix (https://github.com/streamlink/streamlink/pull/1773) [Full changelog](https://github.com/streamlink/streamlink/compare/0.13.0...0.14.0) ## streamlink 0.13.0 (2018-06-06) Massive release this month! Here are the changes: - Initial MPEG DASH support has been added! (https://github.com/streamlink/streamlink/pull/1637) Many thanks to @beardypig - As always, a *ton* of plugin updates - Updates to our documentation (https://github.com/streamlink/streamlink/pull/1673) - Updates to our logging (https://github.com/streamlink/streamlink/pull/1752) as well as log --quiet options (https://github.com/streamlink/streamlink/pull/1744) (https://github.com/streamlink/streamlink/pull/1720) - Our release script has been updated (https://github.com/streamlink/streamlink/pull/1711) - Support for livestreams when using the `--hls-duration` option (https://github.com/streamlink/streamlink/pull/1710) - Allow streamlink to exit faster when using Ctrl+C (https://github.com/streamlink/streamlink/pull/1658) - Added an OpenCV Face Detection example (https://github.com/streamlink/streamlink/pull/1689) [Full changelog](https://github.com/streamlink/streamlink/compare/0.12.1...0.13.0) ## streamlink 0.12.1 (2018-05-07) Streamlink 0.12.1 Small release to fix a pip / Windows.exe generation bug! [Full changelog](https://github.com/streamlink/streamlink/compare/0.12.0...0.12.1) ## streamlink 0.12.0 (2018-05-07) Streamlink 0.12.0 Thanks for all the contributors to this month's release! New updates: - A *ton* of plugin updates (like always! see below for a list of updates) - Ignoring a bunch of useless files when developing (https://github.com/streamlink/streamlink/pull/1570) - A new option to limit the number of fetch retries (https://github.com/streamlink/streamlink/pull/1375) - YouTube has been updated to not use MuxedStream for livestreams (https://github.com/streamlink/streamlink/pull/1556) - Bug fix with ffmpegmux (https://github.com/streamlink/streamlink/pull/1502) - Removed dead plugins and deprecated options (https://github.com/streamlink/streamlink/pull/1546) [Full changelog](https://github.com/streamlink/streamlink/compare/0.11.0...0.12.0) ## streamlink 0.11.0 (2018-03-08) Streamlink 0.11.0! Here's what's new: - Fixed documentation (https://github.com/streamlink/streamlink/pull/1467 and https://github.com/streamlink/streamlink/pull/1468) - Current versions of the OS, Python, Streamlink and Requests are now shown with -l debug (https://github.com/streamlink/streamlink/pull/1374) - ok.ru/live plugin added (https://github.com/streamlink/streamlink/pull/1451) - New option --hls-segment-ignore-names (https://github.com/streamlink/streamlink/pull/1432) - AfreecaTV plugin updates (https://github.com/streamlink/streamlink/pull/1390) - Added support for zattoo recordings (https://github.com/streamlink/streamlink/pull/1480) - Bigo plugin updates (https://github.com/streamlink/streamlink/pull/1474) - Neulion plugin removed due to DMCA notice (https://github.com/streamlink/streamlink/pull/1497) - And many more updates to numerous other plugins! [Full changelog](https://github.com/streamlink/streamlink/compare/0.10.0...0.11.0) ## streamlink 0.10.0 (2018-01-23) Streamlink 0.10.0! There's been a lot of activity since our November release. Changes: - Multiple plugin updates (too many to list, see below for the plugin changes!) - HLS seeking support (https://github.com/streamlink/streamlink/pull/1303) - Changes to the Windows binary (docs: https://github.com/streamlink/streamlink/pull/1408 minor changes to install directory: https://github.com/streamlink/streamlink/pull/1407) [Full changelog](https://github.com/streamlink/streamlink/compare/0.9.0...0.10.0) ## streamlink 0.9.0 (2017-11-14) Streamlink 0.9.0 has been released! This release is mostly code refactoring as well as module inclusion. Features: - Updates to multiple plugins (electrecetv, tvplayer, Teve2, cnnturk, kanald) - SOCKS module being included in the Streamlink installer (PySocks) Many thanks to those who've contributed in this release! [Full changelog](https://github.com/streamlink/streamlink/compare/0.8.1...0.9.0) ## streamlink 0.8.1 (2017-09-12) 0.8.1 of Streamlink! 97 commits have occurred since the last release, including a large majority of plugin changes. Here's the outline of what's new: - Multiple plugin fixes (twitch, vaughlive, hitbox, etc.) - Donations! We've gone ahead and joined the Open Collective at https://opencollective.com/streamlink - Multiple doc updates - Support for SOCKS proxies - Code refactoring Many thanks to those who've contributed in this release! [Full changelog](https://github.com/streamlink/streamlink/compare/0.7.0...0.8.1) ## streamlink 0.7.0 (2017-06-30) 0.7.0 of Streamlink! Since our May release, we've incorporated quite a few changes! Outlined are the major features in this month's release: - Stream types will now be sorted accordingly in terms of quality - TeamLiquid.net Plugin added - Numerous plugin & bug fixes - Updated HomeBrew package - Improved CLI documentation Many thanks to those who've contributed in this release! If you think that this application is helpful, please consider supporting the maintainers by [donating](https://streamlink.github.io/donate.html). [Full changelog](https://github.com/streamlink/streamlink/compare/0.6.0...0.7.0) ## streamlink 0.6.0 (2017-05-11) Another release of Streamlink! We've updated more plugins, improved documentation, and moved out nightly builds to Bintray (S3 was costing *wayyyy* too much). Again, many thanks for those who've contributed! Thank you very much! [Full changelog](https://github.com/streamlink/streamlink/compare/0.5.0...0.6.0) ## streamlink 0.5.0 (2017-04-04) Streamlink 0.5.0! Lot's of contributions since the last release. As always, lot's of updating to plugins! One of the new features is the addition of Google Drive / Google Docs, you can now stream videos stored on Google Docs. We've also gone ahead and removed dead plugins (sites which have gone down) as well as added pycrypto as a dependency for future plugins. Again, many thanks for those who have contributed! Thank you very much! [Full changelog](https://github.com/streamlink/streamlink/compare/0.4.0...0.5.0) ## streamlink 0.4.0 (2017-03-09) 0.4.0 of Streamlink! 114 commits since the last release and *a lot* has changed. In general, we've added some localization as well as an assortment of new plugins. We've also introduced a change for Streamlink to *not* check for new updates each time Streamlink starts. We found this feature annoying as well as delaying the initial start of the stream. This feature can be re-enabled by the command line. The major features of this release are: - New plugins added - Ongoing support to current plugins via bug fixes - Ensure retries to HLS streams - Disable update check Many thanks to all contributors who have contributed in this release! [Full changelog](https://github.com/streamlink/streamlink/compare/0.3.2...0.4.0) ## streamlink 0.3.2 (2017-02-10) 0.3.2 release of Streamlink! A minor bug release of 0.3.2 to fix a few issues with stream providers. Thanks to all whom have contributed to this (tiny) release! [Full changelog](https://github.com/streamlink/streamlink/compare/0.3.1...0.3.2) ## streamlink 0.3.1 (2017-02-03) 0.3.1 release of Streamlink A *minor* release, we update our source code upload to *not* include the ffmpeg.exe binary as well as update a multitude of plugins. Thanks again for all the contributions as well as updates! [Full changelog](https://github.com/streamlink/streamlink/compare/0.3.0...0.3.1) ## streamlink 0.3.0 (2017-01-24) Release 0.3.0 of Streamlink! A lot of updates to each plugin (thank you @beardypig !), automated Windows releases, PEP8 formatting throughout Streamlink are some of the few updates to this release as we near a stable 1.0.0 release. Main features are: - Lot's of maintaining / updates to plugins - General bug and doc fixes - Major improvements to development (github issue templates, automatically created releases) [Full changelog](https://github.com/streamlink/streamlink/compare/0.2.0...0.3.0) ## streamlink 0.2.0 (2016-12-16) Release 0.2.0 of Streamlink! We've done numerous changes to plugins as well as fixed quite a few which were originally failing. Among these changes are updated docs as well as general UI/UX cleaning with console output. The main features are: - Additional plugins added - Plugin fixes - Cleaned up console output - Additional documentation (contribution, installation instructions) Again, thank you everyone whom contributed to this release! :D [Full changelog](https://github.com/streamlink/streamlink/compare/0.1.0...0.2.0) ## streamlink 0.1.0 (2016-11-21) A major update to Streamlink. With this release, we include a Windows binary as well as numerous plugin changes and fixes. The main features are: - Windows binary (and generation!) thanks to the fabulous work by @beardypig - Multiple plugin fixes - Remove unneeded run-as-root (no more warning you when you run as root, we trust that you know what you're doing) - Fix stream quality naming issue [Full changelog](https://github.com/streamlink/streamlink/compare/0.0.2...0.1.0) ## streamlink 0.0.2 (2016-10-12) The second ever release of Streamlink! In this release we've not only set the stepping stone for the further development of Streamlink (documentation site updated, CI builds working) but we're already fixing bugs and implementing features past the initial fork of livestreamer. The main features of this release are: - New windows build available and generated via pyinstaller - Multiple provider bug fixes (twitch, picarto, itvplayer, crunchyroll, periscope, douyutv) - Updated and reformed documentation which also includes our site https://streamlink.github.io As always, below is a `git shortlog` of all changes from the previous release of Streamlink (0.0.1) to now (0.0.2). [Full changelog](https://github.com/streamlink/streamlink/compare/0.0.1...0.0.2) ## streamlink 0.0.1 (2016-09-23) The first release of Streamlink! This is the first release from the initial fork of Livestreamer. We aim to have a concise, fast review process and progress in terms of development and future releases. Below is a `git shortlog` of all commits since the last change within Livestream (hash ab80dbd6560f6f9835865b2fc9f9c6015aee5658). This will serve as a base-point as we continue development of "Streamlink". New releases will include a list of changes as we add new features / code refactors to the existing code-base. [Full changelog](https://github.com/streamlink/streamlink/compare/ab80dbd...0.0.1) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/LICENSE0000644000175100001660000000250615003227510014300 0ustar00runnerdockerCopyright (c) 2011-2016, Christopher Rosell Copyright (c) 2016-2025, Streamlink Team All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/MANIFEST.in0000644000175100001660000000060015003227510015022 0ustar00runnerdockerinclude AUTHORS include CHANGELOG.md include README.md include LICENSE* include *requirements.txt include icon.svg recursive-include build_backend * recursive-include completions * recursive-include docs * recursive-include tests * prune docs/_build include docs/_build/man/* include script/build-shell-completions.sh prune */__pycache__ global-exclude *.pyc *~ *.bak *.swp *.pyo ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745694560.0418198 streamlink-7.3.0/PKG-INFO0000644000175100001660000001453615003227540014401 0ustar00runnerdockerMetadata-Version: 2.4 Name: streamlink Version: 7.3.0 Summary: Streamlink is a command-line utility that extracts streams from various services and pipes them into a video player of choice. Author: Streamlink Author-email: streamlink@protonmail.com License-Expression: BSD-2-Clause Project-URL: Homepage, https://github.com/streamlink/streamlink Project-URL: Documentation, https://streamlink.github.io/ Project-URL: Tracker, https://github.com/streamlink/streamlink/issues Project-URL: Source, https://github.com/streamlink/streamlink Project-URL: Funding, https://streamlink.github.io/latest/support.html Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Console Classifier: Intended Audience :: End Users/Desktop Classifier: Operating System :: POSIX Classifier: Operating System :: MacOS Classifier: Operating System :: Microsoft :: Windows Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: 3.13 Classifier: Topic :: Internet :: WWW/HTTP Classifier: Topic :: Multimedia :: Sound/Audio Classifier: Topic :: Multimedia :: Video Classifier: Topic :: Utilities Requires-Python: >=3.9 Description-Content-Type: text/markdown License-File: LICENSE Requires-Dist: certifi Requires-Dist: exceptiongroup; python_version < "3.11" Requires-Dist: isodate Requires-Dist: lxml<6,>=4.6.4 Requires-Dist: pycountry Requires-Dist: pycryptodome<4,>=3.4.3 Requires-Dist: PySocks!=1.5.7,>=1.5.6 Requires-Dist: requests<3,>=2.26.0 Requires-Dist: trio<1,>=0.25.0; python_version >= "3.13" Requires-Dist: trio<1,>=0.22.0; python_version < "3.13" Requires-Dist: trio-websocket<1,>=0.9.0 Requires-Dist: urllib3<3,>=1.26.0 Requires-Dist: websocket-client<2,>=1.2.1 Provides-Extra: decompress Requires-Dist: urllib3[brotli,zstd]<3,>=1.26.0; extra == "decompress" Dynamic: license-file

Streamlink
Streamlink

Supported Python versions Latest release License Open issues Build status Overall code coverage

A Python library and command-line interface which pipes streams from various services into a video player.
Avoid resource-heavy and unoptimized websites, and still enjoy streamed content.

Streamlink was forked in 2016 from the abandoned Livestreamer project.

# 📦 Installation Please take a look at the documentation for different ways of installing Streamlink: - [Windows][streamlink-installation-windows] - [macOS][streamlink-installation-macos] - [Linux and BSD][streamlink-installation-linux-and-bsd] - [PyPI package and source code][streamlink-installation-pypi-source] # 👠Features Streamlink is built on top of a plugin system which allows support for new services to be added easily. Most of the popular streaming services are supported, such as [Twitch](https://www.twitch.tv), [YouTube](https://www.youtube.com), and many more. A list of all plugins currently included can be found on the [plugins page][streamlink-plugins]. # 💡 Quickstart After installing, simply run: ```sh streamlink "STREAMURL" best ``` The default behavior of Streamlink is to play back streams in the [VLC player][player-vlc], but a lot of other options and output methods are available, such as writing the stream to the filesystem, reading stream metadata, etc. For more in-depth usage, please refer to the [CLI documentation][streamlink-documentation-cli]. An [API guide][streamlink-documentation-apiguide] and [API reference][streamlink-documentation-apiref] is available for Python implementors of Streamlink. # 🙠Contributing All contributions are welcome. Feel free to open a new thread on the issue tracker or submit a new pull request. Please read [CONTRIBUTING.md][contributing] first. Thanks! # â¤ï¸ Support If you think that Streamlink is useful and if you want to keep the project alive, then please consider supporting its maintainers by sending a small and optionally recurring tip via the [available options][support]. Your support is very much appreciated, thank you! [streamlink-installation-windows]: https://streamlink.github.io/install.html#windows [streamlink-installation-macos]: https://streamlink.github.io/install.html#macos [streamlink-installation-linux-and-bsd]: https://streamlink.github.io/install.html#linux-and-bsd [streamlink-installation-pypi-source]: https://streamlink.github.io/install.html#pypi-package-and-source-code [streamlink-documentation-cli]: https://streamlink.github.io/cli.html [streamlink-documentation-apiguide]: https://streamlink.github.io/api_guide.html [streamlink-documentation-apiref]: https://streamlink.github.io/api.html [streamlink-plugins]: https://streamlink.github.io/plugins.html [player-vlc]: https://www.videolan.org/vlc/ [contributing]: https://github.com/streamlink/streamlink/blob/master/CONTRIBUTING.md [support]: https://streamlink.github.io/latest/support.html ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/README.md0000644000175100001660000001045415003227510014553 0ustar00runnerdocker

Streamlink
Streamlink

Supported Python versions Latest release License Open issues Build status Overall code coverage

A Python library and command-line interface which pipes streams from various services into a video player.
Avoid resource-heavy and unoptimized websites, and still enjoy streamed content.

Streamlink was forked in 2016 from the abandoned Livestreamer project.

# 📦 Installation Please take a look at the documentation for different ways of installing Streamlink: - [Windows][streamlink-installation-windows] - [macOS][streamlink-installation-macos] - [Linux and BSD][streamlink-installation-linux-and-bsd] - [PyPI package and source code][streamlink-installation-pypi-source] # 👠Features Streamlink is built on top of a plugin system which allows support for new services to be added easily. Most of the popular streaming services are supported, such as [Twitch](https://www.twitch.tv), [YouTube](https://www.youtube.com), and many more. A list of all plugins currently included can be found on the [plugins page][streamlink-plugins]. # 💡 Quickstart After installing, simply run: ```sh streamlink "STREAMURL" best ``` The default behavior of Streamlink is to play back streams in the [VLC player][player-vlc], but a lot of other options and output methods are available, such as writing the stream to the filesystem, reading stream metadata, etc. For more in-depth usage, please refer to the [CLI documentation][streamlink-documentation-cli]. An [API guide][streamlink-documentation-apiguide] and [API reference][streamlink-documentation-apiref] is available for Python implementors of Streamlink. # 🙠Contributing All contributions are welcome. Feel free to open a new thread on the issue tracker or submit a new pull request. Please read [CONTRIBUTING.md][contributing] first. Thanks! # â¤ï¸ Support If you think that Streamlink is useful and if you want to keep the project alive, then please consider supporting its maintainers by sending a small and optionally recurring tip via the [available options][support]. Your support is very much appreciated, thank you! [streamlink-installation-windows]: https://streamlink.github.io/install.html#windows [streamlink-installation-macos]: https://streamlink.github.io/install.html#macos [streamlink-installation-linux-and-bsd]: https://streamlink.github.io/install.html#linux-and-bsd [streamlink-installation-pypi-source]: https://streamlink.github.io/install.html#pypi-package-and-source-code [streamlink-documentation-cli]: https://streamlink.github.io/cli.html [streamlink-documentation-apiguide]: https://streamlink.github.io/api_guide.html [streamlink-documentation-apiref]: https://streamlink.github.io/api.html [streamlink-plugins]: https://streamlink.github.io/plugins.html [player-vlc]: https://www.videolan.org/vlc/ [contributing]: https://github.com/streamlink/streamlink/blob/master/CONTRIBUTING.md [support]: https://streamlink.github.io/latest/support.html ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745694559.9368184 streamlink-7.3.0/build_backend/0000755000175100001660000000000015003227540016041 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/build_backend/__init__.py0000644000175100001660000000747615003227510020165 0ustar00runnerdockerfrom __future__ import annotations import shlex from typing import Any from setuptools import build_meta as _build_meta # re-export everything from `setuptools.build_meta`, so that we don't have to worry about any hooks which we don't override # https://peps.python.org/pep-0517/ # https://peps.python.org/pep-0660/ # noinspection PyUnresolvedReferences from setuptools.build_meta import * # noqa: F403 from setuptools.command.egg_info import egg_info as _egg_info # ---- def get_requires_for_build_wheel( # type: ignore[no-redef] config_settings: dict | None = None, ) -> list[str]: # pragma: no cover # Streamlink publishes three wheels on PyPI: the generic "any" wheel, the "win32" wheel and the "win-amd64" wheel: # The Windows-wheels are special, because they include a "gui_scripts" entry point, which is used by the `pip` frontend # to generate the "streamlinkw" launcher, which doesn't open a terminal window when launching it from a GUI application. # # In order to build these special Windows-wheels, the `--plat-name=...` CLI argument needs to get passed # to the `bdist_wheel` setuptools command (provided by the `wheel` package). With the introduction of PEP517 however, # setuptools's CLI is not used directly anymore, and instead, we have to build the wheels using the `build` package # and the `--wheel --config-setting=--build-option=...` args, which are then forwarded to setuptools via the PEP517 hooks. # # Since `build==1.0.0` though, the `--config-setting` data now gets passed to all build-backend hooks involved, # even `get_requires_for_build_wheel()`. This results in the build failing, as it executes setuptools's `egg_info` command, # which doesn't accept the `--plat-name` argument or any other `bdist_wheel` command options. # # As a consequence of this, we need to override the build-backend and the `get_requires_for_build_wheel()` hook, so that # we can filter out arguments which are not relevant to the `egg_info` command. _filter_cmd_option_args( config_settings, "--build-option", # types-setuptools-70.3.0.20240710 has the wrong type for # setuptools.command.egg_info.user_options (setuptools._distutils.cmd.Command.user_options) _egg_info.user_options, # type: ignore[arg-type] ) return _build_meta.get_requires_for_build_wheel(config_settings) # ---- def _filter_cmd_option_args( config_settings: dict | None, key: str, # https://github.com/pypa/setuptools/blob/v71.1.0/setuptools/_distutils/fancy_getopt.py#L47-L54 # https://github.com/pypa/setuptools/blob/v71.1.0/setuptools/_distutils/fancy_getopt.py#L152-L156 options: list[tuple[str, str | None, str] | tuple[str, str | None, str, Any]], ) -> None: """Filter out args which are not recognized by a specific command and its options""" if not config_settings or not config_settings.get(key): return parsed = shlex.split(config_settings[key]) result = [] val_next = False for item in parsed: if val_next: val_next = False result.append(item) continue full: str shorthand: str | None for full, shorthand, *_ in options: is_boolean = full[-1] != "=" is_shorthand = shorthand is not None and item == f"-{shorthand}" if not is_boolean and (is_shorthand or item == f"--{full[:-1]}"): val_next = True result.append(item) break if ( is_boolean and (is_shorthand or item == f"--{full}") or not is_boolean and item.startswith(f"--{full}") ): # fmt: skip result.append(item) break if result: config_settings[key] = shlex.join(result) else: del config_settings[key] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/build_backend/commands.py0000644000175100001660000000161015003227510020207 0ustar00runnerdockerfrom __future__ import annotations import logging from pathlib import Path from setuptools import Command from setuptools.command.build_py import build_py from build_backend.plugins_json import build, to_json STREAMLINK_PLUGINS_JSON = Path("streamlink", "plugins", "_plugins.json") __all__ = ["cmdclass"] class StreamlinkBuildPyCommand(build_py, Command): def _build_plugins_json(self) -> None: self.announce("building plugins JSON data", logging.INFO) output = Path(self.build_lib) / STREAMLINK_PLUGINS_JSON output.parent.mkdir(parents=True, exist_ok=True) data = build() with output.open("w", encoding="utf-8") as fd: to_json(data, fd) def build_package_data(self) -> None: super().build_package_data() self._build_plugins_json() cmdclass: dict[str, type[Command]] = { "build_py": StreamlinkBuildPyCommand, } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/build_backend/onbuild.py0000644000175100001660000000721315003227510020047 0ustar00runnerdockerfrom __future__ import annotations import re from collections.abc import Generator from contextlib import contextmanager from dataclasses import dataclass from pathlib import Path from typing import Any, Generic, TypeVar try: # noinspection PyProtectedMember from versioningit.onbuild import SetuptoolsFileProvider # type: ignore[attr-defined] except ImportError: # pragma: no cover @dataclass class SetuptoolsFileProvider: # type: ignore[no-redef] build_dir: Path # noinspection PyUnusedLocal def onbuild( *, is_source: bool, template_fields: dict[str, Any], params: dict[str, Any], # backward compatibility for `versioningit <3.0.0` build_dir: str | Path | None = None, file_provider: SetuptoolsFileProvider | None = None, ): """ Remove the ``versioningit`` build-requirement from Streamlink's source distribution. Also set the static version string in the :mod:`streamlink._version` module when building the sdist/bdist. The version string already gets set by ``versioningit`` when building, so the sdist doesn't need to have ``versioningit`` added as a build-requirement. Previously, the generated version string was only applied to the :mod:`streamlink._version` module while ``versioningit`` was still set as a build-requirement. This custom onbuild hook gets called via the ``tool.versioningit.onbuild`` config in ``pyproject.toml``, since ``versioningit`` does only support modifying one file via its default onbuild hook configuration. """ base_dir: Path if file_provider: base_dir = file_provider.build_dir.resolve() elif build_dir: # pragma: no cover base_dir = Path(build_dir).resolve() else: # pragma: no cover raise RuntimeError("Missing file_provider or build_dir") pkg_dir: Path = base_dir / "src" if is_source else base_dir version: str = template_fields["version"] cmproxy: Proxy[str] # Remove versioningit from ``build-system.requires`` in ``pyproject.toml`` if is_source: with update_file(base_dir / "pyproject.toml") as cmproxy: cmproxy.set( re.sub( r"^(\s*)(\"versioningit\b.+?\",).*$", "\\1# \\2", cmproxy.get(), flags=re.MULTILINE, count=1, ), ) # Set the static version string that gets passed directly to setuptools via ``setup.py``. # This is much easier compared to adding the ``project.version`` field and removing "version" from ``project.dynamic`` # in ``pyproject.toml``. if is_source: with update_file(base_dir / "setup.py") as cmproxy: cmproxy.set( re.sub( r"^(\s*)# (version=\"\",).*$", f'\\1version="{version}",', cmproxy.get(), flags=re.MULTILINE, count=1, ), ) # Overwrite the entire ``streamlink._version`` module with update_file(pkg_dir / "streamlink" / "_version.py") as cmproxy: cmproxy.set(f'__version__ = "{version}"\n') TProxyItem = TypeVar("TProxyItem") class Proxy(Generic[TProxyItem]): def __init__(self, data: TProxyItem): self._data = data def get(self) -> TProxyItem: return self._data def set(self, data: TProxyItem) -> None: self._data = data @contextmanager def update_file(file: Path) -> Generator[Proxy[str], None, None]: with file.open("r+", encoding="utf-8") as fh: proxy = Proxy(fh.read()) yield proxy fh.seek(0) fh.write(proxy.get()) fh.truncate() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/build_backend/plugins_json.py0000644000175100001660000004042715003227510021131 0ustar00runnerdockerfrom __future__ import annotations import argparse import ast import json import re import sys from dataclasses import asdict, dataclass, is_dataclass from pathlib import Path from textwrap import dedent from typing import TYPE_CHECKING, Any, ClassVar, Generic, TextIO, TypeVar if TYPE_CHECKING: from typing_extensions import TypeAlias DEFAULT_PLUGINSPATH = Path(__file__).parents[1] / "src" / "streamlink" / "plugins" PLUGINSJSON_COMMENTS = [ "DO NOT MODIFY!", "This file was auto-generated and its checksum is validated before loading.", "If you want to modify existing plugins, then please see the plugin-sideloading or developing docs:", "https://streamlink.github.io/", ] TListOfConstants: TypeAlias = "list[bool | int | float | str | None]" TConstantOrListOfConstants: TypeAlias = "bool | int | float | str | TListOfConstants | None" TMappingOfConstantOrListOfConstants: TypeAlias = "dict[str, TConstantOrListOfConstants]" class ParseError(ValueError): def __init__(self, message: str, node: ast.AST): super().__init__(message) self.lineno = getattr(node, "lineno", None) self.col_offset = getattr(node, "col_offset", None) @dataclass class PluginMatcher: NO_PRIORITY: ClassVar[int] = 0 LOW_PRIORITY: ClassVar[int] = 10 NORMAL_PRIORITY: ClassVar[int] = 20 HIGH_PRIORITY: ClassVar[int] = 30 pattern: str flags: int | None = None priority: int | None = None name: str | None = None @dataclass class PluginArgument: name: str action: str | None = None nargs: int | str | None = None const: TConstantOrListOfConstants = None default: TConstantOrListOfConstants = None type: str | None = None type_args: TListOfConstants | None = None type_kwargs: TMappingOfConstantOrListOfConstants | None = None choices: TListOfConstants | None = None required: bool | None = None help: str | None = None metavar: str | list[str] | None = None dest: str | None = None requires: str | list[str] | None = None prompt: str | None = None sensitive: bool | None = None argument_name: str | None = None @dataclass class Plugin: matchers: list[PluginMatcher] arguments: list[PluginArgument] _TParseResult = TypeVar("_TParseResult") class Parser(ast.NodeVisitor, Generic[_TParseResult]): def visit(self, node: ast.AST) -> _TParseResult: return super().visit(node) class ParseCall(ast.NodeVisitor): _PARSERS: ClassVar[dict[str, type[ast.NodeVisitor]]] = {} def visit_Call(self, node: ast.Call) -> dict[str, Any]: parsers = self._PARSERS data = {} parsers_list = list(parsers.items()) for idx, arg in enumerate(node.args or []): if idx >= len(parsers_list): raise ParseError("Invalid number of arguments", arg) key, parser = parsers_list[idx] data[key] = parser().visit(arg) for kw in node.keywords: if kw.arg not in parsers: raise ParseError("Invalid keyword argument", kw) parser = parsers[kw.arg] data[kw.arg] = parser().visit(kw.value) return data class ParseConstant(ast.NodeVisitor): NAME: str = "constant" TYPE: type | tuple[type, ...] = str REQUIRED: bool = False def generic_visit(self, node: ast.AST): raise ParseError(f"Invalid {self.NAME}: unknown AST node", node) def visit_Constant(self, node: ast.Constant): if (self.REQUIRED or node.value is not None) and not isinstance(node.value, self.TYPE): raise ParseError(f"Invalid {self.NAME} type", node) return node.value class ParseConstantOrSequenceOfConstants(ParseConstant): def __init__(self): super().__init__() self._sequence = False def _visit_sequence(self, node: ast.List | ast.Tuple): if self._sequence: raise ParseError(f"Invalid {self.NAME} type", node) self._sequence = True return [self.visit(item) for item in node.elts] def visit_List(self, node: ast.List): return self._visit_sequence(node) def visit_Tuple(self, node: ast.Tuple): return self._visit_sequence(node) class ParseSequenceOfConstants(ParseConstantOrSequenceOfConstants): def visit_Constant(self, node: ast.Constant): if (self.REQUIRED or node.value is not None) and not self._sequence: raise ParseError(f"Invalid {self.NAME} type", node) return super().visit_Constant(node) class ParseMappingOfConstants(ParseConstantOrSequenceOfConstants): def __init__(self): super().__init__() self._mapping = False def _visit_sequence(self, node: ast.List | ast.Tuple): if not self._mapping: raise ParseError(f"Invalid {self.NAME} type", node) return super()._visit_sequence(node) def visit_Constant(self, node: ast.Constant): if (self.REQUIRED or node.value is not None) and not self._mapping: raise ParseError(f"Invalid {self.NAME} type", node) return super().visit_Constant(node) def visit_Dict(self, node: ast.Dict): if self._mapping: raise ParseError(f"Invalid {self.NAME} type", node) self._mapping = True return {self.visit(k): self.visit(v) for k, v in zip(node.keys, node.values) if k} class ParseBinOpOr(ast.NodeVisitor): def generic_visit(self, node: ast.AST): raise ParseError("Unsupported node type", node) def visit_BinOp(self, node: ast.BinOp) -> int: if not isinstance(node.op, ast.BitOr): raise ParseError("Unsupported binary operator", node) return self.visit(node.left) | self.visit(node.right) class ParsePluginMatcher(ParseCall, Parser[PluginMatcher]): class ParseReCompile(ParseCall): _RE_VERBOSE_PATTERN = re.compile(r"\n+\s{2,}") class ParsePattern(ParseConstant): NAME = "@pluginmatcher pattern" REQUIRED = True class ParseFlags(ParseBinOpOr): _ATTRIBUTES: ClassVar[dict[str, int]] = { "I": re.RegexFlag.IGNORECASE, "IGNORECASE": re.RegexFlag.IGNORECASE, "S": re.RegexFlag.DOTALL, "DOTALL": re.RegexFlag.DOTALL, "X": re.RegexFlag.VERBOSE, "VERBOSE": re.RegexFlag.VERBOSE, } def visit_Attribute(self, node: ast.Attribute) -> int: if not isinstance(node.value, ast.Name) or node.value.id != "re" or node.attr not in self._ATTRIBUTES: raise ParseError("Invalid attribute", node) return self._ATTRIBUTES[node.attr] _PARSERS: ClassVar[dict[str, type[ast.NodeVisitor]]] = { "pattern": ParsePattern, "flags": ParseFlags, } def generic_visit(self, node: ast.AST): raise ParseError("Invalid @pluginmatcher pattern: unknown AST node", node) def visit_Call(self, node: ast.Call): func = node.func if ( not isinstance(func, ast.Attribute) or func.attr != "compile" or not isinstance(func.value, ast.Name) or func.value.id != "re" ): raise ParseError("Invalid @pluginmatcher pattern: not a compiled regex", func) data = super().visit_Call(node) pattern = data.get("pattern", "") flags = data.get("flags", 0) if not pattern: raise ParseError("Invalid @pluginmatcher pattern: missing pattern", node) if flags & re.RegexFlag.VERBOSE: pattern = self._RE_VERBOSE_PATTERN.sub("", pattern.strip()) return pattern, flags class ParsePriority(ParseConstant, ParseBinOpOr): NAME = "@pluginmatcher priority" TYPE = int _ATTRIBUTES: ClassVar[dict[str, int]] = { "NO_PRIORITY": PluginMatcher.NO_PRIORITY, "LOW_PRIORITY": PluginMatcher.LOW_PRIORITY, "NORMAL_PRIORITY": PluginMatcher.NORMAL_PRIORITY, "HIGH_PRIORITY": PluginMatcher.HIGH_PRIORITY, } def visit_Name(self, node: ast.Name) -> int: if node.id not in self._ATTRIBUTES: raise ParseError("Unknown @pluginmatcher priority name", node) return self._ATTRIBUTES[node.id] class ParseName(ParseConstant): NAME = "@pluginmatcher name" _PARSERS: ClassVar[dict[str, type[ast.NodeVisitor]]] = { "pattern": ParseReCompile, "priority": ParsePriority, "name": ParseName, } def visit_Call(self, node: ast.Call): data = super().visit_Call(node) pattern, flags = data.get("pattern", ("", 0)) priority = data.get("priority", PluginMatcher.priority) name = data.get("name", PluginMatcher.name) if not pattern: raise ParseError("Missing @pluginmatcher pattern", node) return PluginMatcher( pattern=pattern, flags=(None if flags == 0 else flags), priority=priority, name=name, ) class ParsePluginArgument(ParseCall, Parser[PluginArgument]): class ParseName(ParseConstant): NAME = "@pluginargument name" REQUIRED = True class ParseAction(ParseConstant): NAME = "@pluginargument action" class ParseNArgs(ParseConstant): NAME = "@pluginargument nargs" TYPE = int, str class ParseConst(ParseConstantOrSequenceOfConstants): NAME = "@pluginargument const" TYPE = bool, int, float, str class ParseDefault(ParseConstantOrSequenceOfConstants): NAME = "@pluginargument default" TYPE = bool, int, float, str class ParseType(ParseConstant): NAME = "@pluginargument type" class ParseTypeArgs(ParseSequenceOfConstants): NAME = "@pluginargument type_args" TYPE = bool, int, float, str class ParseTypeKwargs(ParseMappingOfConstants): NAME = "@pluginargument type_kwargs" TYPE = bool, int, float, str class ParseChoices(ParseSequenceOfConstants): NAME = "@pluginargument choices" TYPE = bool, int, float, str class ParseRequired(ParseConstant): NAME = "@pluginargument required" TYPE = bool class ParseHelp(ParseConstant): NAME = "@pluginargument help" REQUIRED = True def visit_Attribute(self, node: ast.Attribute): if node.value.id != "argparse" or node.attr != "SUPPRESS": # type: ignore raise ParseError("Invalid @pluginargument help type", node) return argparse.SUPPRESS def visit_Constant(self, node: ast.Constant): return dedent(super().visit_Constant(node)).strip() class ParseMetavar(ParseConstantOrSequenceOfConstants): NAME = "@pluginargument metavar" class ParseDest(ParseConstant): NAME = "@pluginargument dest" class ParseRequires(ParseConstantOrSequenceOfConstants): NAME = "@pluginargument requires" class ParsePrompt(ParseConstant): NAME = "@pluginargument prompt" class ParseSensitive(ParseConstant): NAME = "@pluginargument sensitive" TYPE = bool class ParseArgumentName(ParseConstant): NAME = "@pluginargument argument_name" _PARSERS: ClassVar[dict[str, type[ast.NodeVisitor]]] = { "name": ParseName, "action": ParseAction, "nargs": ParseNArgs, "const": ParseConst, "default": ParseDefault, "type": ParseType, "type_args": ParseTypeArgs, "type_kwargs": ParseTypeKwargs, "choices": ParseChoices, "required": ParseRequired, "help": ParseHelp, "metavar": ParseMetavar, "dest": ParseDest, "requires": ParseRequires, "prompt": ParsePrompt, "sensitive": ParseSensitive, "argument_name": ParseArgumentName, } def visit_Call(self, node: ast.Call): data = super().visit_Call(node) if not data.get("name"): raise ParseError("Missing @pluginargument name", node) return PluginArgument(**data) class PluginVisitor(ast.NodeVisitor): def __init__(self) -> None: super().__init__() self.name: str | None = None self.matchers: list[PluginMatcher] = [] self.arguments: list[PluginArgument] = [] self.exports: bool = False def generic_visit(self, node: ast.AST): pass def visit_Module(self, node: ast.Module): for body in node.body: self.visit(body) def visit_Assign(self, node: ast.Assign): if ( # pragma: no branch isinstance(node.value, ast.Name) and node.value.id == self.name and self.name is not None ): # fmt: skip for target in node.targets: if isinstance(target, ast.Name) and target.id == "__plugin__": # pragma: no branch self.exports = True def visit_ClassDef(self, node: ast.ClassDef): for base in node.bases: if not isinstance(base, ast.Name): continue if base.id == "Plugin": break else: return self.name = node.name for decorator in node.decorator_list: if ( not isinstance(decorator, ast.Call) or not isinstance(decorator.func, ast.Name) ): # fmt: skip continue if decorator.func.id == "pluginmatcher": matcher = ParsePluginMatcher().visit(decorator) self.matchers.append(matcher) elif decorator.func.id == "pluginargument": # pragma: no branch argument = ParsePluginArgument().visit(decorator) self.arguments.append(argument) Output: TypeAlias = "dict[str, Plugin]" class JSONEncoder(json.JSONEncoder): # pragma: no cover @staticmethod def _filter_dataclass_none_value(items: list[tuple[str, Any]]) -> dict[str, Any]: return {key: val for key, val in items if val is not None} def default(self, o: Any) -> Any: # https://github.com/python/mypy/issues/17550 if is_dataclass(o) and not isinstance(o, type): return asdict(o, dict_factory=self._filter_dataclass_none_value) return super().default(o) def build(pluginsdir: Path = DEFAULT_PLUGINSPATH) -> Output: data: Output = {} for file in pluginsdir.glob("*.py"): name = file.name plugin = re.sub(r"\.py$", "", name) source = file.read_text(encoding="utf-8") tree = ast.parse(source, str(file)) visitor = PluginVisitor() try: visitor.visit(tree) except ParseError as err: raise SyntaxError( err.args[0], ( name, (err.lineno or 1), (err.col_offset or 0) + 1, source.split("\n")[(err.lineno or 1) - 1], ), ) from None if not visitor.exports or not visitor.matchers: # pragma: no cover continue data[plugin] = Plugin(visitor.matchers, visitor.arguments) # noinspection PyTypeChecker return dict(sorted(data.items())) def to_json(data: Output, fd: TextIO | None = None, comments: list[str] | None = None, pretty: bool = False) -> None: outputformat = {"separators": (",", ": "), "indent": 2} if pretty else {"separators": (",", ":")} textio: TextIO = fd or sys.stdout for line in PLUGINSJSON_COMMENTS if comments is None else comments: textio.write(f"// {line}\n") json.dump(data, textio, cls=JSONEncoder, **outputformat) # type: ignore[arg-type] if __name__ == "__main__": # pragma: no cover def main(): parser = argparse.ArgumentParser() parser.add_argument("dir", nargs="?", type=Path, default=DEFAULT_PLUGINSPATH) parser.add_argument("--no-comments", action="store_true") parser.add_argument("--pretty", action="store_true") parser.add_argument("-o", "--output", default="-", help="Output file") args = parser.parse_args() data = build(args.dir) options = {"pretty": args.pretty} if args.no_comments: options["comments"] = [] if args.output == "-": to_json(data, **options) else: with open(args.output, "w", encoding="utf-8") as fd: to_json(data, fd, **options) main() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/build_backend/test_build_backend.py0000644000175100001660000000263515003227510022223 0ustar00runnerdockerimport pytest from setuptools.command.egg_info import egg_info from build_backend import _filter_cmd_option_args @pytest.mark.parametrize( ("config_settings", "expected", "options"), [ pytest.param( None, None, egg_info.user_options, id="Empty config_settings", ), pytest.param( {"foo": "bar"}, {"foo": "bar"}, egg_info.user_options, id="No --build-option key", ), pytest.param( {"--build-option": "--egg-base=foo/bar -e baz/qux --tag-build foo -b bar --tag-date --no-date -D"}, {"--build-option": "--egg-base=foo/bar -e baz/qux --tag-build foo -b bar --tag-date --no-date -D"}, egg_info.user_options, id="All egg_info options", ), pytest.param( {"--build-option": "--foo --bar --baz"}, {}, egg_info.user_options, id="Options unknown to egg_info", ), pytest.param( {"--build-option": "-p win32 --plat-name win32 --plat-name=win32"}, {}, egg_info.user_options, id="bdist_wheel --plat-name option", ), ], ) def test_filter_cmd_option_args(config_settings: dict, expected: str, options: list): _filter_cmd_option_args(config_settings, "--build-option", options) assert config_settings == expected ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/build_backend/test_onbuild.py0000644000175100001660000000704115003227510021105 0ustar00runnerdockerimport re import shutil from pathlib import Path import pytest from build_backend.onbuild import onbuild try: # noinspection PyProtectedMember from versioningit.onbuild import SetuptoolsFileProvider # type: ignore[attr-defined] except ImportError: # pragma: no cover _HAS_ONBUILD_FILE_PROVIDER = False else: _HAS_ONBUILD_FILE_PROVIDER = True PROJECT_ROOT = Path(__file__).parents[1] @pytest.fixture() def template_fields(request: pytest.FixtureRequest) -> dict: template_fields = { "version": "1.2.3+fake", } template_fields.update(getattr(request, "param", {})) return template_fields @pytest.fixture(autouse=True) def build(request: pytest.FixtureRequest, tmp_path: Path, template_fields: dict) -> Path: param = getattr(request, "param", {}) is_source = param.get("is_source", True) pkg_dir = tmp_path / "src" if is_source else tmp_path (pkg_dir / "streamlink").mkdir(parents=True) shutil.copy(PROJECT_ROOT / "pyproject.toml", tmp_path / "pyproject.toml") shutil.copy(PROJECT_ROOT / "setup.py", tmp_path / "setup.py") # Lookup for the path from the root source directory if Path(PROJECT_ROOT / "src" / "streamlink" / "_version.py").exists(): shutil.copy(PROJECT_ROOT / "src" / "streamlink" / "_version.py", pkg_dir / "streamlink" / "_version.py") else: # pragma: no cover # We didn't find it, use the build location shutil.copy(PROJECT_ROOT / "streamlink" / "_version.py", pkg_dir / "streamlink" / "_version.py") options = dict( is_source=is_source, template_fields=template_fields, params={}, ) if _HAS_ONBUILD_FILE_PROVIDER: options["file_provider"] = SetuptoolsFileProvider(build_dir=tmp_path) else: # pragma: no cover options["build_dir"] = tmp_path onbuild(**options) return tmp_path @pytest.mark.parametrize("build", [pytest.param({"is_source": True}, id="is_source=True")], indirect=True) def test_sdist(build: Path): assert re.search( r"^(\s*)# (\"versioningit\b.+?\",).*$", (build / "pyproject.toml").read_text(encoding="utf-8"), re.MULTILINE, ), "versioningit is not a build-requirement" assert re.search( # Check for any version string, and not just the fake one, # because tests can be run from the sdist where the version string was already set. # The onbuild hook does only replace the template in `setup.py`. r"^(\s*)(version=\"[^\"]+\",).*$", (build / "setup.py").read_text(encoding="utf-8"), re.MULTILINE, ), "setup() call defines a static version string" assert ( (build / "src" / "streamlink" / "_version.py").read_text(encoding="utf-8") == '__version__ = "1.2.3+fake"\n' ), "streamlink._version exports a static version string" # fmt: skip @pytest.mark.parametrize("build", [pytest.param({"is_source": False}, id="is_source=False")], indirect=True) def test_bdist(build: Path): assert ( (build / "pyproject.toml").read_text(encoding="utf-8") == (PROJECT_ROOT / "pyproject.toml").read_text(encoding="utf-8") ), "Doesn't touch pyproject.toml (irrelevant for non-sdist)" # fmt: skip assert ( (build / "setup.py").read_text(encoding="utf-8") == (PROJECT_ROOT / "setup.py").read_text(encoding="utf-8") ), "Doesn't touch setup.py (irrelevant for non-sdist)" # fmt: skip assert ( (build / "streamlink" / "_version.py").read_text(encoding="utf-8") == '__version__ = "1.2.3+fake"\n' ), "streamlink._version exports a static version string" # fmt: skip ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/build_backend/test_plugins_json.py0000644000175100001660000012221715003227510022166 0ustar00runnerdockerfrom __future__ import annotations import ast import re from contextlib import nullcontext, suppress from pathlib import Path from textwrap import dedent from typing import Any import pytest from build_backend.plugins_json import ( PLUGINSJSON_COMMENTS, ParseConstantOrSequenceOfConstants, ParseMappingOfConstants, ParseSequenceOfConstants, PluginArgument, PluginMatcher, PluginVisitor, build, to_json, ) does_not_raise = nullcontext() class ConstantOrSequenceOfConstants(ParseConstantOrSequenceOfConstants): TYPE = bool, int, float, str REQUIRED = False class SequenceOfConstants(ParseSequenceOfConstants): TYPE = bool, int, float, str REQUIRED = False class MappingOfConstants(ParseMappingOfConstants): TYPE = bool, int, float, str REQUIRED = False @pytest.mark.parametrize( ("parser", "code", "expected", "raises"), [ pytest.param( SequenceOfConstants, """None""", None, does_not_raise, id="soc-optional", ), pytest.param( SequenceOfConstants, """[None,True,False,1,2,3.14,"a"]""", [None, True, False, 1, 2, 3.14, "a"], does_not_raise, id="soc-sequence", ), pytest.param( SequenceOfConstants, """1""", None, pytest.raises(ValueError, match=r"^Invalid constant type$"), id="soc-not-a-sequence", ), pytest.param( SequenceOfConstants, """[1,2,[]]""", None, pytest.raises(ValueError, match=r"^Invalid constant type$"), id="soc-no-nested-sequences", ), pytest.param( ConstantOrSequenceOfConstants, """None""", None, does_not_raise, id="cosoc-optional", ), pytest.param( ConstantOrSequenceOfConstants, """True""", True, does_not_raise, id="cosoc-constant", ), pytest.param( ConstantOrSequenceOfConstants, """[None,True,False,1,2,3.14,"a"]""", [None, True, False, 1, 2, 3.14, "a"], does_not_raise, id="cosoc-sequence", ), pytest.param( ConstantOrSequenceOfConstants, """[1,2,[]]""", None, pytest.raises(ValueError, match=r"^Invalid constant type$"), id="cosoc-no-nested-sequences", ), pytest.param( MappingOfConstants, """None""", None, does_not_raise, id="moc-optional", ), pytest.param( MappingOfConstants, """{"a": True, "b": 1, "c": 3.14, "d": "d", "e": [False, 2, 0.1, "x"]}""", {"a": True, "b": 1, "c": 3.14, "d": "d", "e": [False, 2, 0.1, "x"]}, does_not_raise, id="moc-mapping", ), pytest.param( MappingOfConstants, """1""", None, pytest.raises(ValueError, match=r"^Invalid constant type$"), id="moc-not-a-mapping", ), pytest.param( MappingOfConstants, """[1]""", None, pytest.raises(ValueError, match=r"^Invalid constant type$"), id="moc-no-sequences", ), pytest.param( MappingOfConstants, """{"a": {}}""", None, pytest.raises(ValueError, match=r"^Invalid constant type$"), id="moc-no-nested-mappings", ), ], ) def test_parse_sequence_or_mapping(parser: type[ast.NodeVisitor], code: str, expected: Any, raises: nullcontext): tree = ast.parse(code) item: ast.AST = tree.body[0].value # type: ignore with raises: assert parser().visit(item) == expected @pytest.mark.parametrize( ("code", "expected", "raises"), [ pytest.param( dedent(""" @pluginmatcher(re.compile(r"foo")) class NotAPlugin: pass """), [], does_not_raise, id="no-plugin-subclass", ), pytest.param( dedent(""" @pluginmatcher(re.compile(r"foo")) class NotAPlugin(GetBaseClass(), SomethingElse): pass """), [], does_not_raise, id="no-plugin-subclass2", ), pytest.param( dedent(""" @pluginmatcher class TestPlugin(Plugin): pass """), [], does_not_raise, id="no-decorator-call", ), pytest.param( dedent(""" @pluginmatcher(re.compile(r"foo")) @pluginmatcher(re.compile(pattern=r"foo")) @pluginmatcher(pattern=re.compile(r"foo")) @pluginmatcher(pattern=re.compile(pattern=r"foo")) @pluginmatcher(re.compile(r"bar", re.IGNORECASE)) @pluginmatcher(re.compile(r"bar", flags=re.IGNORECASE)) @pluginmatcher(pattern=re.compile(r"bar", re.IGNORECASE)) @pluginmatcher(pattern=re.compile(r"bar", flags=re.IGNORECASE)) @pluginmatcher(re.compile(r"baz", re.I | re.S | re.X)) @pluginmatcher(re.compile(r"baz", flags=re.I | re.S | re.X)) class TestPlugin(Plugin): pass """), [ PluginMatcher(pattern="foo", flags=None, priority=None, name=None), PluginMatcher(pattern="foo", flags=None, priority=None, name=None), PluginMatcher(pattern="foo", flags=None, priority=None, name=None), PluginMatcher(pattern="foo", flags=None, priority=None, name=None), PluginMatcher(pattern="bar", flags=re.IGNORECASE, priority=None, name=None), PluginMatcher(pattern="bar", flags=re.IGNORECASE, priority=None, name=None), PluginMatcher(pattern="bar", flags=re.IGNORECASE, priority=None, name=None), PluginMatcher(pattern="bar", flags=re.IGNORECASE, priority=None, name=None), PluginMatcher(pattern="baz", flags=re.I | re.S | re.X, priority=None, name=None), PluginMatcher(pattern="baz", flags=re.I | re.S | re.X, priority=None, name=None), ], does_not_raise, id="pattern", ), pytest.param( dedent(""" @pluginmatcher() class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Missing @pluginmatcher pattern$"), id="pattern-missing", ), pytest.param( dedent(""" @pluginmatcher(invalid) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Invalid @pluginmatcher pattern: unknown AST node$"), id="pattern-invalid", ), pytest.param( dedent(""" @pluginmatcher(get_pattern_object()) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Invalid @pluginmatcher pattern: not a compiled regex$"), id="pattern-invalid-regex", ), pytest.param( dedent(""" @pluginmatcher(re.compile(r''' not a verbose regex pattern ''')) @pluginmatcher(re.compile(r''' a\\sverbose[ ]+ regex\\ pattern ''', flags=re.VERBOSE)) class TestPlugin(Plugin): pass """), [ PluginMatcher( pattern=" not a verbose regex pattern ", flags=None, priority=None, name=None, ), PluginMatcher( pattern="a\\sverbose[ ]+regex\\ pattern", flags=re.VERBOSE, priority=None, name=None, ), ], does_not_raise, id="pattern-pattern-verbose", ), pytest.param( dedent(""" @pluginmatcher(re.compile()) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Invalid @pluginmatcher pattern: missing pattern$"), id="pattern-pattern-missing", ), pytest.param( dedent(""" @pluginmatcher(re.compile(foo())) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Invalid @pluginmatcher pattern: unknown AST node$"), id="pattern-pattern-invalid-arg", ), pytest.param( dedent(""" @pluginmatcher(re.compile(pattern=foo())) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Invalid @pluginmatcher pattern: unknown AST node$"), id="pattern-pattern-invalid-kwarg", ), pytest.param( dedent(""" @pluginmatcher(re.compile(r"foo", 123)) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Unsupported node type$"), id="pattern-flag-invalid-node-type", ), pytest.param( dedent(""" @pluginmatcher(re.compile(r"foo", re.MULTILINE)) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Invalid attribute$"), id="pattern-flag-invalid-attribute", ), pytest.param( dedent(""" @pluginmatcher(re.compile(r"foo", re.IGNORECASE & re.VERBOSE)) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Unsupported binary operator$"), id="pattern-flag-invalid-binary-operator", ), pytest.param( dedent(""" @pluginmatcher(re.compile(r"empty")) @pluginmatcher(re.compile(r"0"), 0) @pluginmatcher(re.compile(r"123"), priority=123) @pluginmatcher(re.compile(r"NO"), priority=NO_PRIORITY) @pluginmatcher(re.compile(r"LOW"), priority=LOW_PRIORITY) @pluginmatcher(re.compile(r"NORMAL"), priority=NORMAL_PRIORITY) @pluginmatcher(re.compile(r"HIGH"), priority=HIGH_PRIORITY) class TestPlugin(Plugin): pass """), [ PluginMatcher(pattern="empty", flags=None, priority=None, name=None), PluginMatcher(pattern="0", flags=None, priority=0, name=None), PluginMatcher(pattern="123", flags=None, priority=123, name=None), PluginMatcher(pattern="NO", flags=None, priority=PluginMatcher.NO_PRIORITY, name=None), PluginMatcher(pattern="LOW", flags=None, priority=PluginMatcher.LOW_PRIORITY, name=None), PluginMatcher(pattern="NORMAL", flags=None, priority=PluginMatcher.NORMAL_PRIORITY, name=None), PluginMatcher(pattern="HIGH", flags=None, priority=PluginMatcher.HIGH_PRIORITY, name=None), ], does_not_raise, id="priority", ), pytest.param( dedent(""" @pluginmatcher(re.compile(r"unknown"), priority=foo()) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Invalid @pluginmatcher priority: unknown AST node$"), id="priority-invalid-node-type", ), pytest.param( dedent(""" @pluginmatcher(re.compile(r"unknown"), priority=123.456) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Invalid @pluginmatcher priority type$"), id="priority-invalid-type", ), pytest.param( dedent(""" @pluginmatcher(re.compile(r"unknown"), priority=UNKNOWN) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Unknown @pluginmatcher priority name$"), id="priority-unknown-name", ), pytest.param( dedent(""" @pluginmatcher(re.compile(r"one"), NORMAL_PRIORITY, "one") @pluginmatcher(re.compile(r"two"), NORMAL_PRIORITY, None) @pluginmatcher(re.compile(r"three"), name="three") @pluginmatcher(re.compile(r"four"), name=None) @pluginmatcher(name="five", pattern=re.compile(r"five")) class TestPlugin(Plugin): pass """), [ PluginMatcher(pattern="one", flags=None, priority=PluginMatcher.NORMAL_PRIORITY, name="one"), PluginMatcher(pattern="two", flags=None, priority=PluginMatcher.NORMAL_PRIORITY, name=None), PluginMatcher(pattern="three", flags=None, priority=None, name="three"), PluginMatcher(pattern="four", flags=None, priority=None, name=None), PluginMatcher(pattern="five", flags=None, priority=None, name="five"), ], does_not_raise, id="name", ), pytest.param( dedent(""" @pluginmatcher(re.compile(r"."), name=NAME) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Invalid @pluginmatcher name: unknown AST node$"), id="name-unknown-node-type", ), pytest.param( dedent(""" @pluginmatcher(re.compile(r"."), name=123) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Invalid @pluginmatcher name type$"), id="name-invalid-type", ), pytest.param( dedent(""" @pluginmatcher(re.compile(r"."), NORMAL_PRIORITY, "name", 123) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Invalid number of arguments$"), id="invalid-arguments-number", ), pytest.param( dedent(""" @pluginmatcher(re.compile(r"."), invalid=123) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Invalid keyword argument$"), id="invalid-keyword", ), ], ) def test_pluginmatcher(code: str, expected: list, raises: nullcontext): tree = ast.parse(code) pluginvisitor = PluginVisitor() with raises: pluginvisitor.visit(tree) assert pluginvisitor.matchers == expected @pytest.mark.parametrize( ("code", "expected", "raises"), [ pytest.param( dedent(""" @pluginargument("foo") @pluginargument(name="bar") class TestPlugin(Plugin): pass """), [ PluginArgument(name="foo"), PluginArgument(name="bar"), ], does_not_raise, id="name", ), pytest.param( dedent(""" @pluginargument() class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Missing @pluginargument name$"), id="name-missing", ), pytest.param( dedent(""" @pluginargument(name=INVALID) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Invalid @pluginargument name: unknown AST node$"), id="name-unknown-node-type", ), pytest.param( dedent(""" @pluginargument(name=123) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Invalid @pluginargument name type$"), id="name-invalid-type", ), pytest.param( dedent(""" @pluginargument("foo", action=None) @pluginargument("bar", action="store_true") class TestPlugin(Plugin): pass """), [ PluginArgument(name="foo", action=None), PluginArgument(name="bar", action="store_true"), ], does_not_raise, id="action", ), pytest.param( dedent(""" @pluginargument("foo", action=INVALID) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Invalid @pluginargument action: unknown AST node$"), id="action-unknown-node-type", ), pytest.param( dedent(""" @pluginargument("foo", action=123) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Invalid @pluginargument action type$"), id="action-invalid-type", ), pytest.param( dedent(""" @pluginargument("foo", nargs=None) @pluginargument("bar", nargs=2) @pluginargument("baz", nargs="?") class TestPlugin(Plugin): pass """), [ PluginArgument(name="foo", nargs=None), PluginArgument(name="bar", nargs=2), PluginArgument(name="baz", nargs="?"), ], does_not_raise, id="nargs", ), pytest.param( dedent(""" @pluginargument("foo", nargs=INVALID) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Invalid @pluginargument nargs: unknown AST node$"), id="nargs-unknown-node-type", ), pytest.param( dedent(""" @pluginargument("foo", nargs=123.456) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Invalid @pluginargument nargs type$"), id="nargs-invalid-type", ), pytest.param( dedent(""" @pluginargument("foo", const=None) @pluginargument("bar", const=1) @pluginargument("baz", const=["a", 2, 3.14, True]) @pluginargument("qux", const=("a", 2, 3.14, True)) class TestPlugin(Plugin): pass """), [ PluginArgument(name="foo", const=None), PluginArgument(name="bar", const=1), PluginArgument(name="baz", const=["a", 2, 3.14, True]), PluginArgument(name="qux", const=["a", 2, 3.14, True]), ], does_not_raise, id="const", ), pytest.param( dedent(""" @pluginargument("foo", const=INVALID) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Invalid @pluginargument const: unknown AST node$"), id="const-unknown-node-type", ), pytest.param( dedent(""" @pluginargument("foo", const=[[]]) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Invalid @pluginargument const type$"), id="const-invalid-type", ), pytest.param( dedent(""" @pluginargument("foo", default=None) @pluginargument("bar", default=1) @pluginargument("baz", default=["a", 2, 3.14, True]) @pluginargument("qux", default=("a", 2, 3.14, True)) class TestPlugin(Plugin): pass """), [ PluginArgument(name="foo", default=None), PluginArgument(name="bar", default=1), PluginArgument(name="baz", default=["a", 2, 3.14, True]), PluginArgument(name="qux", default=["a", 2, 3.14, True]), ], does_not_raise, id="default", ), pytest.param( dedent(""" @pluginargument("foo", default=INVALID) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Invalid @pluginargument default: unknown AST node$"), id="default-unknown-node-type", ), pytest.param( dedent(""" @pluginargument("foo", default=[[]]) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Invalid @pluginargument default type$"), id="default-invalid-type", ), pytest.param( dedent(""" @pluginargument("foo", type=None) @pluginargument("bar", type="comma_list") class TestPlugin(Plugin): pass """), [ PluginArgument(name="foo", type=None), PluginArgument(name="bar", type="comma_list"), ], does_not_raise, id="type", ), pytest.param( dedent(""" @pluginargument("foo", type=INVALID) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Invalid @pluginargument type: unknown AST node$"), id="type-unknown-node-type", ), pytest.param( dedent(""" @pluginargument("foo", type=123) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Invalid @pluginargument type type$"), id="type-invalid-type", ), pytest.param( dedent(""" @pluginargument("foo", type_args=None) @pluginargument("bar", type_args=["a", 2, 3.14, True]) @pluginargument("baz", type_args=("a", 2, 3.14, True)) class TestPlugin(Plugin): pass """), [ PluginArgument(name="foo", type_args=None), PluginArgument(name="bar", type_args=["a", 2, 3.14, True]), PluginArgument(name="baz", type_args=["a", 2, 3.14, True]), ], does_not_raise, id="type-args", ), pytest.param( dedent(""" @pluginargument("foo", type_args=INVALID) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Invalid @pluginargument type_args: unknown AST node$"), id="type-args-unknown-node-type", ), pytest.param( dedent(""" @pluginargument("foo", type_args=1) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Invalid @pluginargument type_args type$"), id="type-args-invalid-type", ), pytest.param( dedent(""" @pluginargument("foo", type_kwargs=None) @pluginargument("bar", type_kwargs={"a": True, "b": 1, "c": 3.14, "d": "d", "e": [False, 2, 0.1, "x"]}) class TestPlugin(Plugin): pass """), [ PluginArgument(name="foo", type_kwargs=None), PluginArgument(name="bar", type_kwargs={"a": True, "b": 1, "c": 3.14, "d": "d", "e": [False, 2, 0.1, "x"]}), ], does_not_raise, id="type-kwargs", ), pytest.param( dedent(""" @pluginargument("foo", type_kwargs=INVALID) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Invalid @pluginargument type_kwargs: unknown AST node$"), id="type-kwargs-unknown-node-type", ), pytest.param( dedent(""" @pluginargument("foo", type_kwargs=1) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Invalid @pluginargument type_kwargs type$"), id="type-kwargs-invalid-type", ), pytest.param( dedent(""" @pluginargument("foo", choices=None) @pluginargument("bar", choices=["a", 2, 3.14, True]) @pluginargument("baz", choices=("a", 2, 3.14, True)) class TestPlugin(Plugin): pass """), [ PluginArgument(name="foo", choices=None), PluginArgument(name="bar", choices=["a", 2, 3.14, True]), PluginArgument(name="baz", choices=["a", 2, 3.14, True]), ], does_not_raise, id="choices", ), pytest.param( dedent(""" @pluginargument("foo", choices=INVALID) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Invalid @pluginargument choices: unknown AST node$"), id="choices-unknown-node-type", ), pytest.param( dedent(""" @pluginargument("foo", choices=1) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Invalid @pluginargument choices type$"), id="choices-invalid-type", ), pytest.param( dedent(""" @pluginargument("foo", required=None) @pluginargument("bar", required=True) @pluginargument("baz", required=False) class TestPlugin(Plugin): pass """), [ PluginArgument(name="foo", required=None), PluginArgument(name="bar", required=True), PluginArgument(name="baz", required=False), ], does_not_raise, id="required", ), pytest.param( dedent(""" @pluginargument("foo", required=INVALID) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Invalid @pluginargument required: unknown AST node$"), id="required-unknown-node-type", ), pytest.param( dedent(""" @pluginargument("foo", required=1) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Invalid @pluginargument required type$"), id="required-invalid-type", ), pytest.param( dedent(""" @pluginargument("foo", help=argparse.SUPPRESS) @pluginargument("bar", help="==SUPPRESS==") @pluginargument("baz", help="help") class TestPlugin(Plugin): pass """), [ PluginArgument(name="foo", help="==SUPPRESS=="), PluginArgument(name="bar", help="==SUPPRESS=="), PluginArgument(name="baz", help="help"), ], does_not_raise, id="help", ), pytest.param( dedent(""" @pluginargument("foo", help=other.SUPPRESS) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Invalid @pluginargument help type$"), id="help-not-argparse-suppress", ), pytest.param( dedent(""" @pluginargument("foo", help=INVALID) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Invalid @pluginargument help: unknown AST node$"), id="help-unknown-node-type", ), pytest.param( dedent(""" @pluginargument("foo", help=1) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Invalid @pluginargument help type$"), id="help-invalid-type", ), pytest.param( dedent(""" @pluginargument("foo", metavar=None) @pluginargument("bar", metavar="bar") @pluginargument("baz", metavar=["foo", "bar"]) class TestPlugin(Plugin): pass """), [ PluginArgument(name="foo", metavar=None), PluginArgument(name="bar", metavar="bar"), PluginArgument(name="baz", metavar=["foo", "bar"]), ], does_not_raise, id="metavar", ), pytest.param( dedent(""" @pluginargument("foo", metavar=INVALID) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Invalid @pluginargument metavar: unknown AST node$"), id="metavar-unknown-node-type", ), pytest.param( dedent(""" @pluginargument("foo", metavar=[1]) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Invalid @pluginargument metavar type$"), id="metavar-invalid-type", ), pytest.param( dedent(""" @pluginargument("foo", dest=None) @pluginargument("bar", dest="bar") class TestPlugin(Plugin): pass """), [ PluginArgument(name="foo", dest=None), PluginArgument(name="bar", dest="bar"), ], does_not_raise, id="dest", ), pytest.param( dedent(""" @pluginargument("foo", dest=INVALID) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Invalid @pluginargument dest: unknown AST node$"), id="dest-unknown-node-type", ), pytest.param( dedent(""" @pluginargument("foo", dest=1) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Invalid @pluginargument dest type$"), id="dest-invalid-type", ), pytest.param( dedent(""" @pluginargument("foo", requires=None) @pluginargument("bar", requires="foo") @pluginargument("baz", requires=["foo", "bar"]) class TestPlugin(Plugin): pass """), [ PluginArgument(name="foo", requires=None), PluginArgument(name="bar", requires="foo"), PluginArgument(name="baz", requires=["foo", "bar"]), ], does_not_raise, id="requires", ), pytest.param( dedent(""" @pluginargument("foo", requires=INVALID) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Invalid @pluginargument requires: unknown AST node$"), id="requires-unknown-node-type", ), pytest.param( dedent(""" @pluginargument("foo", requires=1) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Invalid @pluginargument requires type$"), id="requires-invalid-type", ), pytest.param( dedent(""" @pluginargument("foo", prompt=None) @pluginargument("bar", prompt="bar") class TestPlugin(Plugin): pass """), [ PluginArgument(name="foo", prompt=None), PluginArgument(name="bar", prompt="bar"), ], does_not_raise, id="prompt", ), pytest.param( dedent(""" @pluginargument("foo", prompt=INVALID) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Invalid @pluginargument prompt: unknown AST node$"), id="prompt-unknown-node-type", ), pytest.param( dedent(""" @pluginargument("foo", prompt=1) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Invalid @pluginargument prompt type$"), id="prompt-invalid-type", ), pytest.param( dedent(""" @pluginargument("foo", sensitive=None) @pluginargument("bar", sensitive=True) @pluginargument("baz", sensitive=False) class TestPlugin(Plugin): pass """), [ PluginArgument(name="foo", sensitive=None), PluginArgument(name="bar", sensitive=True), PluginArgument(name="baz", sensitive=False), ], does_not_raise, id="sensitive", ), pytest.param( dedent(""" @pluginargument("foo", sensitive=INVALID) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Invalid @pluginargument sensitive: unknown AST node$"), id="sensitive-unknown-node-type", ), pytest.param( dedent(""" @pluginargument("foo", sensitive=1) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Invalid @pluginargument sensitive type$"), id="sensitive-invalid-type", ), pytest.param( dedent(""" @pluginargument("foo", argument_name=None) @pluginargument("bar", argument_name="bar") class TestPlugin(Plugin): pass """), [ PluginArgument(name="foo", argument_name=None), PluginArgument(name="bar", argument_name="bar"), ], does_not_raise, id="argument-name", ), pytest.param( dedent(""" @pluginargument("foo", argument_name=INVALID) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Invalid @pluginargument argument_name: unknown AST node$"), id="argument-name-unknown-node-type", ), pytest.param( dedent(""" @pluginargument("foo", argument_name=1) class TestPlugin(Plugin): pass """), None, pytest.raises(ValueError, match=r"^Invalid @pluginargument argument_name type$"), id="argument-name-invalid-type", ), ], ) def test_pluginargument(code: str, expected: list, raises: nullcontext): tree = ast.parse(code) pluginvisitor = PluginVisitor() with raises: pluginvisitor.visit(tree) assert pluginvisitor.arguments == expected @pytest.mark.parametrize( ("code", "expected"), [ pytest.param( dedent(""" class TestPlugin(Plugin): pass """), False, id="does-not-export", ), pytest.param( dedent(""" class TestPlugin(Plugin): pass __plugin__ = TestPlugin """), True, id="exports", ), ], ) def test_exports(code: str, expected: bool): tree = ast.parse(code) pluginvisitor = PluginVisitor() pluginvisitor.visit(tree) assert pluginvisitor.exports == expected @pytest.fixture() def test_plugin_code(request: pytest.FixtureRequest): faulty = getattr(request, "param", {}).get("faulty", False) return dedent(f""" import argparse import re from streamlink.plugin import LOW_PRIORITY, Plugin, pluginargument, pluginmatcher @pluginmatcher(re.compile("https://a")) @pluginmatcher(name="b", pattern=re.compile("https://b", re.IGNORECASE), priority=LOW_PRIORITY) @pluginmatcher(name="c", pattern=re.compile("https://c", re.I | re.X), priority=20) @pluginargument( "a", type="comma_list_filter", type_kwargs={{"acceptable": ["a", "b"]}}, default=["a"], help="a", ) @pluginargument( "b", requires="a", sensitive={"True" if not faulty else "INVALID"}, help=argparse.SUPPRESS, ) class TestPlugin(Plugin): def _get_streams(self): return None __plugin__ = TestPlugin """).strip() @pytest.fixture() def test_plugins_dir(tmp_path: Path, test_plugin_code: str): file_path = tmp_path / "testplugin.py" try: with file_path.open("w", encoding="utf-8") as fp: fp.write(test_plugin_code) yield tmp_path finally: with suppress(FileNotFoundError): file_path.unlink() def test_plugin(test_plugin_code: str): tree = ast.parse(test_plugin_code) pluginvisitor = PluginVisitor() pluginvisitor.visit(tree) assert pluginvisitor.exports assert pluginvisitor.matchers == [ PluginMatcher(pattern="https://a", flags=None, priority=None, name=None), PluginMatcher(pattern="https://b", flags=re.IGNORECASE, priority=PluginMatcher.LOW_PRIORITY, name="b"), PluginMatcher(pattern="https://c", flags=re.I | re.X, priority=20, name="c"), ] assert pluginvisitor.arguments == [ PluginArgument(name="a", type="comma_list_filter", type_kwargs={"acceptable": ["a", "b"]}, default=["a"], help="a"), PluginArgument(name="b", requires="a", sensitive=True, help="==SUPPRESS=="), ] @pytest.mark.parametrize( ("comments", "expected_comments"), [ pytest.param( None, PLUGINSJSON_COMMENTS, id="default", ), pytest.param( ["foo", "bar"], ["foo", "bar"], id="custom", ), pytest.param( [], [], id="empty", ), ], ) def test_build( capsys: pytest.CaptureFixture, test_plugins_dir: Path, comments: list[str] | None, expected_comments: list[str], ): data = build(test_plugins_dir) to_json(data, comments=comments, pretty=True) out, _err = capsys.readouterr() assert ( out == "".join(f"// {comment}\n" for comment in expected_comments) + dedent( """ { "testplugin": { "matchers": [ { "pattern": "https://a" }, { "pattern": "https://b", "flags": 2, "priority": 10, "name": "b" }, { "pattern": "https://c", "flags": 66, "priority": 20, "name": "c" } ], "arguments": [ { "name": "a", "default": [ "a" ], "type": "comma_list_filter", "type_kwargs": { "acceptable": [ "a", "b" ] }, "help": "a" }, { "name": "b", "help": "==SUPPRESS==", "requires": "a", "sensitive": true } ] } } """, ).strip() ) @pytest.mark.parametrize("test_plugin_code", [{"faulty": True}], indirect=True) def test_build_faulty(capsys: pytest.CaptureFixture, test_plugins_dir: Path, test_plugin_code: str): with pytest.raises(SyntaxError) as cm: build(test_plugins_dir) out, err = capsys.readouterr() assert not out assert not err assert cm.value msg, (filename, lineno, column, line) = cm.value.args assert msg == "Invalid @pluginargument sensitive: unknown AST node" assert filename == "testplugin.py" assert lineno == 20 assert column == 15 assert line == " sensitive=INVALID," assert cm.value.__cause__ is None ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745694559.9248183 streamlink-7.3.0/completions/0000755000175100001660000000000015003227540015627 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745694559.9368184 streamlink-7.3.0/completions/bash/0000755000175100001660000000000015003227540016544 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694556.0 streamlink-7.3.0/completions/bash/streamlink0000644000175100001660000002452715003227534020655 0ustar00runnerdocker# AUTOMATICALLY GENERATED by `shtab` _shtab_streamlink_cli_option_strings=('-h' '--help' '-V' '--version' '--version-check' '--auto-version-check' '--plugins' '--plugin-dir' '--plugin-dirs' '--show-matchers' '--can-handle-url' '--can-handle-url-no-redirect' '--config' '--no-config' '--locale' '-l' '--loglevel' '--logformat' '--logdateformat' '--logfile' '-Q' '--quiet' '-j' '--json' '--interface' '-4' '--ipv4' '-6' '--ipv6' '-p' '--player' '-a' '--player-args' '--player-env' '-v' '--player-verbose' '--verbose-player' '-n' '--player-fifo' '--fifo' '--player-http' '--player-continuous-http' '--player-external-http' '--player-external-http-continuous' '--player-external-http-interface' '--player-external-http-port' '--player-passthrough' '--player-no-close' '-t' '--title' '-O' '--stdout' '-o' '--output' '-r' '--record' '-R' '--record-and-pipe' '--fs-safe-rules' '-f' '--force' '--progress' '--url' '--default-stream' '--stream-url' '--retry-streams' '--retry-max' '--retry-open' '--stream-types' '--stream-priority' '--stream-sorting-excludes' '--ringbuffer-size' '--stream-segment-attempts' '--stream-segment-threads' '--stream-segment-timeout' '--stream-timeout' '--mux-subtitles' '--hls-live-edge' '--hls-segment-stream-data' '--hls-playlist-reload-attempts' '--hls-playlist-reload-time' '--hls-segment-queue-threshold' '--hls-segment-ignore-names' '--hls-segment-key-uri' '--hls-audio-select' '--hls-start-offset' '--hls-duration' '--hls-live-restart' '--dash-manifest-reload-attempts' '--ffmpeg-ffmpeg' '--ffmpeg-no-validation' '--ffmpeg-verbose' '--ffmpeg-verbose-path' '--ffmpeg-loglevel' '--ffmpeg-fout' '--ffmpeg-video-transcode' '--ffmpeg-audio-transcode' '--ffmpeg-copyts' '--ffmpeg-start-at-zero' '--http-proxy' '--http-cookie' '--http-header' '--http-query-param' '--http-ignore-env' '--http-no-ssl-verify' '--http-disable-dh' '--http-ssl-cert' '--http-ssl-cert-crt-key' '--http-timeout' '--webbrowser' '--webbrowser-executable' '--webbrowser-timeout' '--webbrowser-cdp-host' '--webbrowser-cdp-port' '--webbrowser-cdp-timeout' '--webbrowser-headless' '--bbciplayer-username' '--bbciplayer-password' '--bbciplayer-hd' '--clubbingtv-username' '--clubbingtv-password' '--niconico-email' '--niconico-password' '--niconico-user-session' '--niconico-purge-credentials' '--niconico-timeshift-offset' '--openrectv-email' '--openrectv-password' '--pixiv-sessionid' '--pixiv-devicetoken' '--pixiv-purge-credentials' '--pixiv-performer' '--raiplay-email' '--raiplay-password' '--raiplay-purge-credentials' '--soop-username' '--soop-password' '--soop-purge-credentials' '--soop-stream-password' '--steam-email' '--steam-password' '--streann-url' '--tf1-email' '--tf1-password' '--tf1-purge-credentials' '--twitcasting-password' '--twitch-disable-ads' '--twitch-low-latency' '--twitch-api-header' '--twitch-access-token-param' '--twitch-force-client-integrity' '--twitch-purge-client-integrity' '--ustream-password' '--ustvnow-username' '--ustvnow-password' '--wwenetwork-email' '--wwenetwork-password' '--yupptv-boxid' '--yupptv-yuppflixtoken' '--yupptv-purge-credentials' '--zattoo-email' '--zattoo-password' '--zattoo-purge-credentials' '--zattoo-stream-types') _shtab_streamlink_cli__l_choices=('none' 'critical' 'error' 'warning' 'info' 'debug' 'trace' 'all') _shtab_streamlink_cli___loglevel_choices=('none' 'critical' 'error' 'warning' 'info' 'debug' 'trace' 'all') _shtab_streamlink_cli___fs_safe_rules_choices=('POSIX' 'Windows') _shtab_streamlink_cli___progress_choices=('yes' 'force' 'no') _shtab_streamlink_cli__h_nargs=0 _shtab_streamlink_cli___help_nargs=0 _shtab_streamlink_cli__V_nargs=0 _shtab_streamlink_cli___version_nargs=0 _shtab_streamlink_cli___version_check_nargs=0 _shtab_streamlink_cli___plugins_nargs=0 _shtab_streamlink_cli___no_config_nargs=0 _shtab_streamlink_cli__Q_nargs=0 _shtab_streamlink_cli___quiet_nargs=0 _shtab_streamlink_cli__j_nargs=0 _shtab_streamlink_cli___json_nargs=0 _shtab_streamlink_cli__4_nargs=0 _shtab_streamlink_cli___ipv4_nargs=0 _shtab_streamlink_cli__6_nargs=0 _shtab_streamlink_cli___ipv6_nargs=0 _shtab_streamlink_cli__v_nargs=0 _shtab_streamlink_cli___player_verbose_nargs=0 _shtab_streamlink_cli___verbose_player_nargs=0 _shtab_streamlink_cli__n_nargs=0 _shtab_streamlink_cli___player_fifo_nargs=0 _shtab_streamlink_cli___fifo_nargs=0 _shtab_streamlink_cli___player_http_nargs=0 _shtab_streamlink_cli___player_continuous_http_nargs=0 _shtab_streamlink_cli___player_external_http_nargs=0 _shtab_streamlink_cli___player_no_close_nargs=0 _shtab_streamlink_cli__O_nargs=0 _shtab_streamlink_cli___stdout_nargs=0 _shtab_streamlink_cli__f_nargs=0 _shtab_streamlink_cli___force_nargs=0 _shtab_streamlink_cli___stream_url_nargs=0 _shtab_streamlink_cli___mux_subtitles_nargs=0 _shtab_streamlink_cli___hls_segment_stream_data_nargs=0 _shtab_streamlink_cli___hls_live_restart_nargs=0 _shtab_streamlink_cli___ffmpeg_no_validation_nargs=0 _shtab_streamlink_cli___ffmpeg_verbose_nargs=0 _shtab_streamlink_cli___ffmpeg_copyts_nargs=0 _shtab_streamlink_cli___ffmpeg_start_at_zero_nargs=0 _shtab_streamlink_cli___http_ignore_env_nargs=0 _shtab_streamlink_cli___http_no_ssl_verify_nargs=0 _shtab_streamlink_cli___http_disable_dh_nargs=0 _shtab_streamlink_cli___http_ssl_cert_crt_key_nargs=2 _shtab_streamlink_cli___bbciplayer_hd_nargs=0 _shtab_streamlink_cli___niconico_purge_credentials_nargs=0 _shtab_streamlink_cli___pixiv_purge_credentials_nargs=0 _shtab_streamlink_cli___raiplay_purge_credentials_nargs=0 _shtab_streamlink_cli___soop_purge_credentials_nargs=0 _shtab_streamlink_cli___afreeca_purge_credentials_nargs=0 _shtab_streamlink_cli___tf1_purge_credentials_nargs=0 _shtab_streamlink_cli___twitch_disable_ads_nargs=0 _shtab_streamlink_cli___twitch_disable_hosting_nargs=0 _shtab_streamlink_cli___twitch_disable_reruns_nargs=0 _shtab_streamlink_cli___twitch_low_latency_nargs=0 _shtab_streamlink_cli___twitch_force_client_integrity_nargs=0 _shtab_streamlink_cli___twitch_purge_client_integrity_nargs=0 _shtab_streamlink_cli___yupptv_purge_credentials_nargs=0 _shtab_streamlink_cli___zattoo_purge_credentials_nargs=0 # $1=COMP_WORDS[1] _shtab_compgen_files() { compgen -f -- $1 # files } # $1=COMP_WORDS[1] _shtab_compgen_dirs() { compgen -d -- $1 # recurse into subdirs } # $1=COMP_WORDS[1] _shtab_replace_nonword() { echo "${1//[^[:word:]]/_}" } # set default values (called for the initial parser & any subparsers) _set_parser_defaults() { local subparsers_var="${prefix}_subparsers[@]" sub_parsers=${!subparsers_var-} local current_option_strings_var="${prefix}_option_strings[@]" current_option_strings=${!current_option_strings_var} completed_positional_actions=0 _set_new_action "pos_${completed_positional_actions}" true } # $1=action identifier # $2=positional action (bool) # set all identifiers for an action's parameters _set_new_action() { current_action="${prefix}_$(_shtab_replace_nonword $1)" local current_action_compgen_var=${current_action}_COMPGEN current_action_compgen="${!current_action_compgen_var-}" local current_action_choices_var="${current_action}_choices[@]" current_action_choices="${!current_action_choices_var-}" local current_action_nargs_var="${current_action}_nargs" if [ -n "${!current_action_nargs_var-}" ]; then current_action_nargs="${!current_action_nargs_var}" else current_action_nargs=1 fi current_action_args_start_index=$(( $word_index + 1 - $pos_only )) current_action_is_positional=$2 } # Notes: # `COMPREPLY`: what will be rendered after completion is triggered # `completing_word`: currently typed word to generate completions for # `${!var}`: evaluates the content of `var` and expand its content as a variable # hello="world" # x="hello" # ${!x} -> ${hello} -> "world" _shtab_streamlink_cli() { local completing_word="${COMP_WORDS[COMP_CWORD]}" local previous_word="${COMP_WORDS[COMP_CWORD-1]}" local completed_positional_actions local current_action local current_action_args_start_index local current_action_choices local current_action_compgen local current_action_is_positional local current_action_nargs local current_option_strings local sub_parsers COMPREPLY=() local prefix=_shtab_streamlink_cli local word_index=0 local pos_only=0 # "--" delimeter not encountered yet _set_parser_defaults word_index=1 # determine what arguments are appropriate for the current state # of the arg parser while [ $word_index -ne $COMP_CWORD ]; do local this_word="${COMP_WORDS[$word_index]}" if [[ $pos_only = 1 || " $this_word " != " -- " ]]; then if [[ -n $sub_parsers && " ${sub_parsers[@]} " == *" ${this_word} "* ]]; then # valid subcommand: add it to the prefix & reset the current action prefix="${prefix}_$(_shtab_replace_nonword $this_word)" _set_parser_defaults fi if [[ " ${current_option_strings[@]} " == *" ${this_word} "* ]]; then # a new action should be acquired (due to recognised option string or # no more input expected from current action); # the next positional action can fill in here _set_new_action $this_word false fi if [[ "$current_action_nargs" != "*" ]] && \ [[ "$current_action_nargs" != "+" ]] && \ [[ "$current_action_nargs" != *"..." ]] && \ (( $word_index + 1 - $current_action_args_start_index - $pos_only >= \ $current_action_nargs )); then $current_action_is_positional && let "completed_positional_actions += 1" _set_new_action "pos_${completed_positional_actions}" true fi else pos_only=1 # "--" delimeter encountered fi let "word_index+=1" done # Generate the completions if [[ $pos_only = 0 && "${completing_word}" == -* ]]; then # optional argument started: use option strings COMPREPLY=( $(compgen -W "${current_option_strings[*]}" -- "${completing_word}") ) elif [[ "${previous_word}" == ">" || "${previous_word}" == ">>" || "${previous_word}" =~ ^[12]">" || "${previous_word}" =~ ^[12]">>" ]]; then # handle redirection operators COMPREPLY=( $(compgen -f -- "${completing_word}") ) else # use choices & compgen local IFS=$'\n' # items may contain spaces, so delimit using newline COMPREPLY=( $([ -n "${current_action_compgen}" ] \ && "${current_action_compgen}" "${completing_word}") ) unset IFS COMPREPLY+=( $(compgen -W "${current_action_choices[*]}" -- "${completing_word}") ) fi return 0 } complete -o filenames -F _shtab_streamlink_cli streamlink ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745694559.9378185 streamlink-7.3.0/completions/zsh/0000755000175100001660000000000015003227540016433 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694556.0 streamlink-7.3.0/completions/zsh/_streamlink0000644000175100001660000011343015003227534020673 0ustar00runnerdocker#compdef streamlink # AUTOMATICALLY GENERATED by `shtab` _shtab_streamlink_cli_commands() { local _commands=( ) _describe 'streamlink commands' _commands } _shtab_streamlink_cli_options=( {-h,--help}"[ Show this help message and exit. ]" "(- : *)"{-V,--version}"[ Show version string and exit. ]" "--version-check[ Run a version check and exit. ]" "--auto-version-check[ Enable or disable the automatic check for a new version of Streamlink. Default is \"no\". ]:auto_version_check:" "--plugins[ Print a list of all currently installed plugins. ]" "*--plugin-dir[ Load plugins from this directory. Can be set multiple times to load plugins from multiple directories. ]:plugin_dirs:" "*--plugin-dirs[ Load plugins from a list of comma-separated directories. (deprecated) ]:plugin_dirs:" "--show-matchers[ Show the list of matchers of a specific plugin (URL regex pattern with opt. priority and opt. name). The output is a human-readable pseudo YAML format. Please use --json when reading matcher data programmatically. ]:show_matchers:" "--can-handle-url[ Check if Streamlink has a plugin that can handle the specified URL. Status code is \`0\` on success, \`1\` on failure. Useful for external scripting. ]:can_handle_url:" "--can-handle-url-no-redirect[ Same as --can-handle-url, but without following redirects when looking up the URL. ]:can_handle_url_no_redirect:" "*--config[ Load options from this config file. Can be repeated to load multiple files, in which case the options are merged on top of each other where the last config has highest priority. ]:config:" "--no-config[ Disable loading any default or custom config files. ]" "--locale[ Override the system\'s locale setting, for selecting the preferred subtitle and audio language. The locale is formatted as \`\[language_code\]_\[country_code\]\`, e.g. \`en_US\` or \`es_ES\`. Default is system locale. ]:locale:" {-l,--loglevel}"[ Set the log message threshold. Valid levels are, in order of increasing verbosity\: \`none\`, \`critical\`, \`error\`, \`warning\`, \`info\`, \`debug\`, \`trace\`, \`all\` Default is \"info\". ]:loglevel:(none critical error warning info debug trace all)" "--logformat[ Set a custom logging format. See the Python standard library\'s \`logging.Formatter\` docs for more information about the logging format and the available \`LogRecord\` attributes. Streamlink\'s formatter uses the curly brace style. The default format depends on the chosen log level. For verbose levels (\`trace\` and \`all\`)\: Default is \"\[\{asctime\}\]\[\{name\}\]\[\{levelname\}\] \{message\}\". Otherwise\: Default is \"\[\{name\}\]\[\{levelname\}\] \{message\}\". ]:logformat:" "--logdateformat[ Set a custom logging date format. This formats the \`LogRecord\`\'s \`asctime\` attribute via \`strftime()\`. The default date format depends on the chosen log level. For verbose levels (\`trace\` and \`all\`)\: Default is \"\%H\:\%M\:\%S.\%f\". Otherwise\: Default is \"\%H\:\%M\:\%S\". ]:logdateformat:" "--logfile[ Append log output to \`FILE\` instead of writing to stdout\/stderr. User prompts and download progress won\'t be written to \`FILE\`. A value of \`-\` (dash) will set the file name to an ISO8601-like string and will choose the following default log directories. Windows\: \%TEMP\%\\streamlink\\logs macOS\: \$\{HOME\}\/Library\/Logs\/streamlink Linux\/BSD\: \$\{XDG_STATE_HOME\:-\$\{HOME\}\/.local\/state\}\/streamlink\/logs ]:logfile:" {-Q,--quiet}"[ Suppress all console and log output, and also disable user prompts. ]" {-j,--json}"[ Output JSON representations instead of the normal text output. Useful for external scripting. ]" "--interface[ Set the network interface. ]:interface:" {-4,--ipv4}"[ Resolve address names to IPv4 only. This option overrides --ipv6. ]" {-6,--ipv6}"[ Resolve address names to IPv6 only. This option overrides --ipv4. ]" {-p,--player}"[ Set the player executable that will be launched (unless a different output method was chosen). Either set an absolute or relative path to the player executable, or just set the executable\'s name if it can be resolved from the paths of the system\'s \`PATH\` environment variable. In addition to setting the player executable path, custom player arguments can be set via --player-args. Note\: In the past, --player allowed defining additional player arguments, which as a consequence required wrapping player paths that contained spaces in quotation marks. This is unsupported since release \`6.0.0\`. Default is VLC player, if available. ]:player:" {-a,--player-args}"[ Set a string of custom --player launch arguments that will be parsed and tokenized. The value can contain formatting variables surrounded by curly braces, \`\{\` and \`\}\`. Curly brace characters can be escaped by doubling, e.g. \`\{\{\` and \`\}\}\`. Available formatting variables\: \`\{playerinput\}\` This is the input argument that the --player will receive. For standard input (stdin), it is \`-\` (dash), but it can also be a file path or URL, depending on the options used. If unset, then the player input argument will be appended to the parsed player arguments list. \`\{playertitleargs\}\` The automatically generated player title arguments, if a supported --player was found. See --title for more. If unset, automatically generated player title arguments will be prepended to the parsed player arguments list. Example\: streamlink -p vlc -a \"--play-and-exit --no-one-instance\" \ \[stream\] Default is \"\". ]:player_args:" "*--player-env[ Add an additional environment variable to the spawned --player process, in addition to the ones inherited from the Streamlink\/Python parent process. This allows setting player environment variables in config files. Can be repeated to add multiple environment variables. ]:player_env:" {-v,--player-verbose}"[ Write the --player\'s stdout\/stderr output to Streamlink\'s stdout\/stderr output. ]" "--verbose-player[ Deprecated in favor of --player-verbose. ]" {-n,--player-fifo}"[ Make the --player read the stream through a named pipe instead of the stdin pipe. ]" "--fifo[ Deprecated in favor of --player-fifo. ]" "--player-http[ Make the --player read the stream through HTTP instead of the stdin pipe. ]" "--player-continuous-http[ Make the --player read the stream through HTTP, but unlike --player-http, it will continuously try to open the stream if the player requests it. This enables the handling of stream disconnects if the player is capable of reconnecting to a HTTP stream. This is usually done by setting the player to a \"repeat mode\". ]" "--player-external-http[ Serve stream data through HTTP without opening the --player. This is useful to allow external devices like smartphones or streaming boxes to watch streams they wouldn\'t be able to otherwise. The default behavior is similar to the --player-continuous-http option, but no player program will be started, and the server will listen on all available connections instead of just in the local (loopback) interface. See --player-external-http-interface for choosing a specific network interface, and see --player-external-http-port for choosing a non-randomized port. Optionally, the --player-external-http-continuous option allows for disabling the continuous run-mode, so that Streamlink will stop when the stream ends. The URLs that can be used to access the stream will be printed to the console, and the server can be interrupted using CTRL-C. ]" "--player-external-http-continuous[ Set the run-mode of --player-external-http to continuous or non-continuous. In the continuous run-mode, Streamlink will keep running after the stream has ended and will wait for the next HTTP request being made unless it gets shut down via CTRL-C. If set to non-continuous, Streamlink will stop once the stream has ended. Default is true. ]:player_external_http_continuous:" "--player-external-http-interface[ Set the network interface on which the HTTP server will be listening on. If unset or set to \`0.0.0.0\`, all available interfaces will be bound. ]:player_external_http_interface:" "--player-external-http-port[ Set the port of the external HTTP server if that mode is enabled. Omit or set to \`0\` to use a random high ( \>1024) port. ]:player_external_http_port:" "--player-passthrough[ A comma-delimited list of stream types to pass to the --player as a URL to let it handle the transport of the stream instead of Streamlink. Stream types that can be converted into a playable URL are\: hls, http Make sure the player can handle the stream type when using this. ]:player_passthrough:" "--player-no-close[ By default, Streamlink will close the --player when the stream ends. This is to avoid \"dead\" GUI players lingering after Streamlink has exited. It does however have the side-effect of sometimes closing a player before it has played back all of its cached data. This option will instead let the player decide when to exit. ]" {-t,--title}"[ Change the title of the --player\'s window. Please see the \"Metadata variables\" section of Streamlink\'s CLI documentation for all available metadata variables, as well as the \"Plugins\" section for the list of metadata variables defined in each plugin. Only the following players are supported\: mpv, potplayer, vlc Example\: streamlink -p mpv --title \"\{author\} - \{category\} - \{title\}\" \ \[STREAM\] ]:title:" {-O,--stdout}"[ Write stream data to \`stdout\` instead of playing it in the --player. ]" {-o,--output}"[ Write stream data to \`FILENAME\` instead of playing it in the --player. If \`FILENAME\` is set to \`-\` (dash), then the stream data will be written to \`stdout\`, similar to the --stdout argument. Directories and subdirectories will be created if they do not exist, if filesystem permissions allow. Unless --force is set, Streamlink will ask for confirmation before writing if \`FILENAME\` already exists. Please see the \"Metadata variables\" section of Streamlink\'s CLI documentation for all available metadata variables, as well as the \"Plugins\" section for the list of metadata variables defined in each plugin. Unsupported characters in substituted variables will be replaced with an underscore. Example\: streamlink --output \"\~\/recordings\/\{author\}\/\{category\}\/\{id\}-\{time\:\%Y\%m\%d\%H\%M\%S\}.ts\" \ \[STREAM\] ]:output:" {-r,--record}"[ Write stream data to \`FILENAME\` while at the same time allowing playback in the --player or writing it to --stdout. If \`FILENAME\` is set to \`-\` (dash), then the stream data will be written to \`stdout\`, similar to the --stdout argument, while still opening the player. Directories and subdirectories will be created if they do not exist, if filesystem permissions allow. Unless --force is set, Streamlink will ask for confirmation before writing if \`FILENAME\` already exists. Please see the \"Metadata variables\" section of Streamlink\'s CLI documentation for all available metadata variables, as well as the \"Plugins\" section for the list of metadata variables defined in each plugin. Unsupported characters in substituted variables will be replaced with an underscore. Example\: streamlink --record \"\~\/recordings\/\{author\}\/\{category\}\/\{id\}-\{time\:\%Y\%m\%d\%H\%M\%S\}.ts\" \ \[STREAM\] ]:record:" {-R,--record-and-pipe}"[ Deprecated in favor of --stdout --record\=FILENAME. ]:record_and_pipe:" "--fs-safe-rules[ The rules used to make formatting variables filesystem-safe are chosen automatically according to the type of system in use. This overrides the automatic detection. Intended for use when Streamlink is running on a UNIX-like OS but writing to Windows filesystems such as NTFS\; USB devices using VFAT or exFAT\; CIFS shares that are enforcing Windows filename limitations, etc. These characters are replaced with an underscore for the rules in use\: - POSIX\: \`\\x00-\\x1F \/\` - Windows\: \`\\x00-\\x1F \\x7F \" \* \/ \: \< \> \? \\ \|\` ]:fs_safe_rules:(POSIX Windows)" {-f,--force}"[ When using --output or --record, always write to file even if it already exists (overwrite). ]" "--progress[ When using --output or --record, show or hide the download progress bar, or force it if there\'s no terminal. Default is yes. ]:progress:(yes force no)" "--url[ A URL to attempt to extract streams from. Usually, the protocol of http(s) URLs can be omitted (\`https\:\/\/\`), depending on the implementation of the plugin being used. This is an alternative to setting the URL using a positional argument and can be useful if set in a config file. ]:url_param:" "--default-stream[ Stream to play. Use \`best\` or \`worst\` for selecting the highest or lowest available quality. Fallback streams can be specified by using a comma-separated list\: \"720p,480p,best\" This is an alternative to setting the stream using a positional argument and can be useful if set in a config file. ]:default_stream:" "--stream-url[ If possible, translate the resolved stream to a URL and print it. ]" "--retry-streams[ Retry fetching the list of available streams until streams are found while waiting \`DELAY\` second(s) between each attempt. The number of retry attempts can be capped with --retry-max. A default value of \`\`1\`\` is implied for non-zero values of --retry-max. If both --retry-streams and --retry-max are set to \`0\`, then only one attempt will be made to fetch the list of available streams. This is the default behavior. ]:retry_streams:" "--retry-max[ Stop fetching the list of available streams after \`COUNT\` retry attempt(s). A value of \`0\` makes Streamlink fetch streams indefinitely if --retry-streams is set to a non-zero value. If --retry-streams is unset, then the default delay between fetching available streams is 1 second. ]:retry_max:" "--retry-open[ After a successful fetch, try \`ATTEMPTS\` time(s) to open the stream until giving up. Default is 1. ]:retry_open:" {--stream-types,--stream-priority}"[ A comma-delimited list of stream types to allow. The order will be used to separate streams when there are multiple streams with the same name but different stream types. Any stream type not listed will be omitted from the available streams list. An \`\*\` (asterisk) can be used as a wildcard to match any other type of stream, e.g. dash. Default is \"hls,http,\*\". ]:stream_types:" "--stream-sorting-excludes[ Fine-tune the \`best\` and \`worst\` stream name synonyms by excluding unwanted streams. If all of the available streams get excluded, \`best\` and \`worst\` will become inaccessible and new special stream synonyms \`best-unfiltered\` and \`worst-unfiltered\` can be used as a fallback selection method. The filter-expression\'s format is\: \[operator\]\ Valid operators are \`\>\`, \`\>\=\`, \`\<\` and \`\<\=\`. If no operator is specified then equality is tested. For example this will exclude streams ranked higher than \"480p\"\: --stream-sorting-excludes \"\>480p\" Multiple filters can be used by separating each expression with a comma. For example this will exclude streams from two quality types\: --stream-sorting-excludes \"\>480p,\>medium\" ]:stream_sorting_excludes:" "--ringbuffer-size[ The maximum size of the ringbuffer. Mebibytes or kibibytes (base 2) can be specified via the M or K suffix respectively. The ringbuffer is used as a temporary storage between the stream and the player. This allows Streamlink to download the stream faster than the player which reads the data from the ringbuffer. The smaller the size of the ringbuffer, the higher the chance of the player buffering if the download speed decreases, and the higher the size, the more data can be use as a storage to recover from volatile download speeds. Most players have their own additional cache and will read the ringbuffer\'s content as soon as data is available. If the player stops reading data while playback is paused, Streamlink will continue to download the stream in the background as long as the ringbuffer doesn\'t get full. Default is \"16M\". ]:ringbuffer_size:" "--stream-segment-attempts[ The number of download attempts of each stream segment before giving up. This applies to all different kinds of segmented stream types, such as DASH, HLS, etc. Default is 3. ]:stream_segment_attempts:" "--stream-segment-threads[ The size of the thread pool used to download segments. Minimum value is \`1\` and maximum is \`10\`. This applies to all different kinds of segmented stream types, such as DASH, HLS, etc. Default is 1. ]:stream_segment_threads:" "--stream-segment-timeout[ The maximum time to wait for each segment to start downloading. This applies to all different kinds of segmented stream types, such as DASH, HLS, etc. Default is 10.0. ]:stream_segment_timeout:" "--stream-timeout[ The maximum time to wait for an unfiltered stream to continue outputting data. This applies to all different kinds of stream types, such as DASH, HLS, HTTP, etc. Default is 60.0. ]:stream_timeout:" "--mux-subtitles[ Automatically mux available subtitles into the output stream. Needs to be supported by the used plugin. ]" "--hls-live-edge[ Number of segments from the live stream\'s current live position to begin streaming. The size or length of each segment is determined by the streaming provider. Lower values will decrease the latency, but will also increase the chance of buffering, as there is less time for Streamlink to download segments and write their data to the output buffer. The number of parallel segment downloads can be set with --stream-segment-threads and the HLS playlist reload time to fetch and queue new segments can be overridden with --hls-playlist-reload-time. Default is 3. Note\: During live playback, the caching\/buffering settings of the used player will add additional latency. To adjust this, please refer to the player\'s own documentation for the required configuration. Player parameters can be set via --player-args. ]:hls_live_edge:" "--hls-segment-stream-data[ Immediately write segment data into output buffer while downloading. ]" "--hls-playlist-reload-attempts[ The maximum number of attempts when reloading the HLS playlist before giving up. Default is 3. ]:hls_playlist_reload_attempts:" "--hls-playlist-reload-time[ Set a custom HLS playlist reload time value, either in seconds or by using one of the following keywords\: - segment\: The duration of the last segment in the current playlist - live-edge\: The sum of segment durations of the live edge value minus one - default\: The playlist\'s target duration metadata Default is default. ]:hls_playlist_reload_time:" "--hls-segment-queue-threshold[ The multiplication factor of the HLS playlist\'s target duration after which the stream will be stopped early if no new segments were queued after refreshing the playlist (multiple times). The target duration defines the maximum duration a single segment can have, meaning new segments must be available during this time frame, otherwise playback issues can occur. The intention of this queue threshold is to be able to stop early when the end of a stream doesn\'t get announced by the server, so Streamlink doesn\'t have to wait until a read-timeout occurs. See --stream-timeout. Set to \`\`0\`\` to disable. Default is 3. ]:hls_segment_queue_threshold:" "--hls-segment-ignore-names[ A comma-delimited list of segment names that will get filtered out. Example\: --hls-segment-ignore-names 000,001,002 This will ignore every segment that ends with 000.ts, 001.ts and 002.ts Default is None. ]:hls_segment_ignore_names:" "--hls-segment-key-uri[ Override the segment encryption key URIs for encrypted streams. The value can be templated using the following variables, which will be replaced with their respective part from the source segment URI\: \{url\} \{scheme\} \{netloc\} \{path\} \{query\} Examples\: --hls-segment-key-uri \"https\:\/\/example.com\/hls\/encryption_key\" --hls-segment-key-uri \"\{scheme\}\:\/\/1.2.3.4\{path\}\{query\}\" --hls-segment-key-uri \"\{scheme\}\:\/\/\{netloc\}\/custom\/path\/to\/key\" Default is None. ]:hls_segment_key_uri:" "--hls-audio-select[ Select one or more specific audio sources by language code or name. Can be set to \`\*\` (asterisk) to include all audio sources. Examples\: --hls-audio-select \"English,German\" --hls-audio-select \"en,de\" --hls-audio-select \"\*\" Note\: This is only useful in special circumstances where the regular locale option fails, such as when multiple sources of the same language exist. ]:hls_audio_select:" "--hls-start-offset[ The amount of time to skip from the beginning of the stream. For live streams, this is a negative offset from the end of the stream (rewind). Default is 0. ]:hls_start_offset:" "--hls-duration[ Limit the playback duration, useful for watching segments of a stream. The actual duration may be slightly longer, as it is rounded to the nearest HLS segment. Default is unlimited. ]:hls_duration:" "--hls-live-restart[ Skip to the beginning of a live stream, or as far back as possible. ]" "--dash-manifest-reload-attempts[ The maximum number of attempts when reloading the DASH manifest before giving up. Default is 3. ]:dash_manifest_reload_attempts:" "--ffmpeg-ffmpeg[ Set the location of the FFmpeg executable if it can\'t be resolved from the paths of the system\'s \`PATH\` environment variable. FFmpeg is required to access or mux separate video and audio streams, e.g. in DASH streams or HLS streams with multiple sources. Example\: --ffmpeg-ffmpeg \"\/usr\/local\/bin\/ffmpeg\" ]:ffmpeg_ffmpeg:" "--ffmpeg-no-validation[ Disable FFmpeg validation and version logging. ]" "--ffmpeg-verbose[ Write FFmpeg\'s stderr output to Streamlink\'s stderr output. ]" "--ffmpeg-verbose-path[ Write FFmpeg\'s stderr output to PATH. ]:ffmpeg_verbose_path:" "--ffmpeg-loglevel[ Change FFmpeg\'s \`-loglevel\` value to \`LOGLEVEL\`. Unless --ffmpeg-verbose or --ffmpeg-verbose-path is set, changing the log level won\'t have any effect. Default is \"info\". ]:ffmpeg_loglevel:" "--ffmpeg-fout[ Set the output format to \`OUTFORMAT\`. This only applies to streams which require muxing. Default is \"matroska\". Example\: --ffmpeg-fout \"mpegts\" ]:ffmpeg_fout:" "--ffmpeg-video-transcode[ Transcode the video to \`CODEC\`. This only applies to streams which require muxing. Default is \"copy\". Example\: --ffmpeg-video-transcode \"h264\" ]:ffmpeg_video_transcode:" "--ffmpeg-audio-transcode[ Transcode the audio to \`CODEC\`. This only applies to streams which require muxing. Default is \"copy\". Example\: --ffmpeg-audio-transcode \"aac\" ]:ffmpeg_audio_transcode:" "--ffmpeg-copyts[ Set the \`-copyts\` FFmpeg option, so input timestamps won\'t be processed and the initial start time offset value be kept. ]" "--ffmpeg-start-at-zero[ Enable the \`-start_at_zero\` FFmpeg option when using --ffmpeg-copyts. ]" "--http-proxy[ An HTTP proxy to use for all HTTP and HTTPS requests, including WebSocket connections. Example\: --http-proxy \"http\:\/\/hostname\:port\/\" ]:http_proxy:" "*--http-cookie[ A cookie to add to each HTTP request. Can be repeated to add multiple cookies. ]:http_cookie:" "*--http-header[ A header to add to each HTTP request. Can be repeated to add multiple headers. ]:http_header:" "*--http-query-param[ A query parameter to add to each HTTP request. Can be repeated to add multiple query parameters. ]:http_query_param:" "--http-ignore-env[ Ignore HTTP settings set in the environment, such as environment variables (\`HTTP_PROXY\`, etc) or \`\~\/.netrc\` authentication. ]" "--http-no-ssl-verify[ Don\'t attempt to verify TLS\/SSL certificates. Use with caution, as it has TLS\/SSL security implications. ]" "--http-disable-dh[ Disable Diffie Hellman key exchange. Use with caution, as it has TLS\/SSL security implications. ]" "--http-ssl-cert[ SSL certificate to use\: a .pem file. ]:http_ssl_cert:" "--http-ssl-cert-crt-key[ SSL certificate to use\: a .crt and a .key file. ]:http_ssl_cert_crt_key:" "--http-timeout[ Set the general timeout value used by all HTTP requests except the ones covered by other options. Default is 20.0. ]:http_timeout:" "--webbrowser[ Enable or disable support for Streamlink\'s webbrowser API. Streamlink\'s webbrowser API allows plugins which implement it to launch a web browser and extract data from websites which they otherwise couldn\'t do via the regular HTTP session in Python due to specific JavaScript restrictions. The web browser is run isolated and in a clean environment without access to regular user data. Streamlink currently only supports Chromium-based web browsers using the Chrome Devtools Protocol (CDP). This includes Chromium itself, Google Chrome, Microsoft Edge, Brave, Vivaldi, and others, but full support for third party Chromium forks is not guaranteed. Please try Chromium or Google Chrome when encountering any issues. Default is true. ]:webbrowser:" "--webbrowser-executable[ Path to the web browser\'s executable. By default, it is looked up automatically according to the rules of the used webbrowser API implementation. This usually involves a list of known executable names and fallback paths on all supported operating systems. ]:webbrowser_executable:" "--webbrowser-timeout[ The maximum amount of time which the web browser can take to launch and execute. ]:webbrowser_timeout:" "--webbrowser-cdp-host[ Host for the web browser\'s inter-process communication interface (CDP specific). Default is 127.0.0.1. ]:webbrowser_cdp_host:" "--webbrowser-cdp-port[ Port for the web browser\'s inter-process communication interface (CDP specific). Tries to find a free port by default. ]:webbrowser_cdp_port:" "--webbrowser-cdp-timeout[ The maximum amount of time for waiting on a single CDP command response. ]:webbrowser_cdp_timeout:" "--webbrowser-headless[ Whether to launch the web browser in headless mode or not. When enabled, it stays completely hidden and doesn\'t require a desktop environment to run. Please be aware that headless mode might be blocked by websites which implement bot detections. Default is false. ]:webbrowser_headless:" "--bbciplayer-username[The username used to register with bbc.co.uk.]:bbciplayer_username:" "--bbciplayer-password[A bbc.co.uk account password to use with --bbciplayer-username.]:bbciplayer_password:" "--bbciplayer-hd[Prefer HD streams over local SD streams, some live programmes may not be broadcast in HD.]" "--clubbingtv-username[The username used to register with Clubbing TV.]:clubbingtv_username:" "--clubbingtv-password[A Clubbing TV account password to use with --clubbingtv-username.]:clubbingtv_password:" "--niconico-email[The email or phone number associated with your Niconico account]:niconico_email:" "--niconico-password[The password of your Niconico account]:niconico_password:" "--niconico-user-session[ Value of the user-session token. Can be used as an alternative to providing a password. ]:niconico_user_session:" "--niconico-purge-credentials[Purge cached Niconico credentials to initiate a new session and reauthenticate.]" "--niconico-timeshift-offset[ Amount of time to skip from the beginning of a stream. Default is 0. ]:niconico_timeshift_offset:" "--openrectv-email[The email associated with your openrectv account, required to access any openrectv stream.]:openrectv_email:" "--openrectv-password[An openrectv account password to use with --openrectv-email.]:openrectv_password:" "--pixiv-sessionid[The pixiv.net sessionid that\'s used in pixiv\'s PHPSESSID cookie.]:pixiv_sessionid:" "--pixiv-devicetoken[The pixiv.net device token that\'s used in pixiv\'s device_token cookie.]:pixiv_devicetoken:" "--pixiv-purge-credentials[Purge cached Pixiv credentials to initiate a new session and reauthenticate.]" "--pixiv-performer[Select a co-host stream instead of the owner stream.]:pixiv_performer:" "--raiplay-email[The email used to register with raiplay.it.]:raiplay_email:" "--raiplay-password[A raiplay.it account password to use with --raiplay-email.]:raiplay_password:" "--raiplay-purge-credentials[Purge cached RaiPlay credentials to initiate a new session and reauthenticate.]" "--soop-username[The username used to register with sooplive.co.kr.]:soop_username:" "--soop-password[A sooplive.co.kr account password to use with --soop-username.]:soop_password:" "--soop-purge-credentials[Purge cached Soop credentials to initiate a new session and reauthenticate.]" "--soop-stream-password[The password for the stream.]:soop_stream_password:" "--steam-email[A Steam account email address to access friends\/private streams]:steam_email:" "--steam-password[A Steam account password to use with --steam-email.]:steam_password:" "--streann-url[Source URL where the iframe is located, only required for direct URLs of ott.streann.com]:streann_url:" "--tf1-email[The email address used to register with tf1.fr.]:tf1_email:" "--tf1-password[A tf1.fr account password to use with --tf1-email.]:tf1_password:" "--tf1-purge-credentials[Purge cached tf1.fr credentials to initiate a new session and reauthenticate.]" "--twitcasting-password[Password for private Twitcasting streams.]:twitcasting_password:" "--twitch-disable-ads[ Skip embedded advertisement segments at the beginning or during a stream. Will cause these segments to be missing from the output. ]" "--twitch-low-latency[ Enables low latency streaming by prefetching HLS segments. Sets --hls-segment-stream-data to true and --hls-live-edge to 2, if it is higher. Reducing --hls-live-edge to \`1\` will result in the lowest latency possible, but will most likely cause buffering. In order to achieve true low latency streaming during playback, the player\'s caching\/buffering settings will need to be adjusted and reduced to a value as low as possible, but still high enough to not cause any buffering. This depends on the stream\'s bitrate and the quality of the connection to Twitch\'s servers. Please refer to the player\'s own documentation for the required configuration. Player parameters can be set via --player-args. Note\: Low latency streams have to be enabled by the broadcasters on Twitch themselves. Regular streams can cause buffering issues with this option enabled due to the reduced --hls-live-edge value. ]" "*--twitch-api-header[ A header to add to each Twitch API HTTP request. Can be repeated to add multiple headers. Useful for adding authentication data that can prevent ads. See the plugin-specific documentation for more information. ]:twitch_api_header:" "*--twitch-access-token-param[ A parameter to add to the API request for acquiring the streaming access token. Can be repeated to add multiple parameters. ]:twitch_access_token_param:" "--twitch-force-client-integrity[Don\'t attempt requesting the streaming access token without a client-integrity token.]" "--twitch-purge-client-integrity[Purge cached Twitch client-integrity token and acquire a new one.]" "--ustream-password[A password to access password protected UStream.tv channels.]:ustream_password:" "--ustvnow-username[Your USTV Now account username]:ustvnow_username:" "--ustvnow-password[Your USTV Now account password]:ustvnow_password:" "--wwenetwork-email[The email associated with your WWE Network account, required to access any WWE Network stream.]:wwenetwork_email:" "--wwenetwork-password[A WWE Network account password to use with --wwenetwork-email.]:wwenetwork_password:" "--yupptv-boxid[The yupptv.com boxid that\'s used in the BoxId cookie.]:yupptv_boxid:" "--yupptv-yuppflixtoken[The yupptv.com yuppflixtoken that\'s used in the YuppflixToken cookie.]:yupptv_yuppflixtoken:" "--yupptv-purge-credentials[Purge cached YuppTV credentials to initiate a new session and reauthenticate.]" "--zattoo-email[The email associated with your zattoo account, required to access any zattoo stream.]:zattoo_email:" "--zattoo-password[A zattoo account password to use with --zattoo-email.]:zattoo_password:" "--zattoo-purge-credentials[Purge cached zattoo credentials to initiate a new session and reauthenticate.]" "--zattoo-stream-types[ A comma-delimited list of stream types which should be used. The following types are allowed\: dash, hls7 Default is \"dash\". ]:zattoo_stream_types:" ":A URL to attempt to extract streams from.:" ":Stream to play.:" ) _shtab_streamlink_cli() { local context state line curcontext="$curcontext" one_or_more='(-)*' remainder='(*)' if ((${_shtab_streamlink_cli_options[(I)${(q)one_or_more}*]} + ${_shtab_streamlink_cli_options[(I)${(q)remainder}*]} == 0)); then # noqa: E501 _shtab_streamlink_cli_options+=(': :_shtab_streamlink_cli_commands' '*::: :->streamlink') fi _arguments -C -s $_shtab_streamlink_cli_options case $state in streamlink) words=($line[1] "${words[@]}") (( CURRENT += 1 )) curcontext="${curcontext%:*:*}:_shtab_streamlink_cli-$line[1]:" case $line[1] in esac esac } typeset -A opt_args if [[ $zsh_eval_context[-1] == eval ]]; then # eval/source/. command, register function for later compdef _shtab_streamlink_cli -N streamlink else # autoload from fpath, call function directly _shtab_streamlink_cli "$@" fi ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/dev-requirements.txt0000644000175100001660000000073515003227510017335 0ustar00runnerdocker# setup pip >=21.0.0 setuptools >=64.0.0 # required for being able to run editable installs versioningit >=2.0.0,<4 # required for being able to run editable installs # tests pytest >=8.0.0 pytest-trio requests-mock freezegun >=1.5.0 # code-coverage pytest-cov coverage[toml] # code-linting / code-formatting ruff ==0.11.5 # typing mypy[faster-cache] ==1.15.0 typing-extensions >=4.0.0 lxml-stubs trio-typing types-freezegun types-requests types-setuptools types-urllib3 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745694559.9418185 streamlink-7.3.0/docs/0000755000175100001660000000000015003227540014223 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/Makefile0000644000175100001660000001326515003227510015667 0ustar00runnerdocker# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = -W SPHINXBUILD = sphinx-build PAPER = DOCSDIR = . BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(DOCSDIR) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(DOCSDIR) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(DOCSDIR) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(DOCSDIR) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(DOCSDIR) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(DOCSDIR) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(DOCSDIR) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/streamlink.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/streamlink.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(DOCSDIR) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/streamlink" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/streamlink" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(DOCSDIR) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(DOCSDIR) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(DOCSDIR) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(DOCSDIR) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) -t man $(DOCSDIR) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(DOCSDIR) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(DOCSDIR) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(DOCSDIR) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(DOCSDIR) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(DOCSDIR) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(DOCSDIR) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745694559.9248183 streamlink-7.3.0/docs/_build/0000755000175100001660000000000015003227540015461 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745694559.9418185 streamlink-7.3.0/docs/_build/man/0000755000175100001660000000000015003227540016234 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694555.0 streamlink-7.3.0/docs/_build/man/streamlink.10000644000175100001660000012241315003227533020474 0ustar00runnerdocker.\" Man page generated from reStructuredText. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "STREAMLINK" "1" "Apr 26, 2025" "7.3.0" "Streamlink" .SH NAME streamlink \- extracts streams from various services and pipes them into a video player of choice .SH SYNOPSIS .INDENT 0.0 .INDENT 3.5 .sp .EX streamlink [OPTIONS] [STREAM] .EE .UNINDENT .UNINDENT .SH EXAMPLES .INDENT 0.0 .INDENT 3.5 .sp .EX streamlink \-\-loglevel debug youtu.be/VIDEO\-ID best streamlink \-\-player mpv \-\-player\-args \(aq\-\-no\-border \-\-no\-keepaspect\-window\(aq twitch.tv/CHANNEL 1080p60 streamlink \-\-player\-external\-http \-\-player\-external\-http\-port 8888 URL STREAM streamlink \-\-output /path/to/file \-\-http\-timeout 60 URL STREAM streamlink \-\-stdout URL STREAM | ffmpeg \-i pipe:0 ... streamlink \-\-http\-header \(aqAuthorization=OAuth TOKEN\(aq \-\-http\-header \(aqReferer=URL\(aq URL STREAM streamlink \-\-hls\-live\-edge 5 \-\-stream\-segment\-threads 5 \(aqhls://https://host/playlist.m3u8\(aq best streamlink \-\-twitch\-low\-latency \-p mpv \-a \(aq\-\-cache=yes \-\-demuxer\-max\-back\-bytes=2G\(aq twitch.tv/CHANNEL best .EE .UNINDENT .UNINDENT .SH OPTIONS .SS Positional arguments .INDENT 0.0 .TP .B URL A URL to attempt to extract streams from. .sp Usually, the protocol of http(s) URLs can be omitted (\fBhttps://\fP), depending on the implementation of the plugin being used. .sp Alternatively, the URL can also be specified by using the \fI\%\-\-url\fP option. .UNINDENT .INDENT 0.0 .TP .B STREAM Stream to play. .sp Use \fBbest\fP or \fBworst\fP for selecting the highest or lowest available quality. .sp Fallback streams can be specified by using a comma\-separated list: .INDENT 7.0 .INDENT 3.5 .sp .EX \(dq720p,480p,best\(dq .EE .UNINDENT .UNINDENT .sp If no stream is specified and \fI\%\-\-default\-stream\fP is not used, then a list of available streams will be printed. .UNINDENT .SS General options .INDENT 0.0 .TP .B \-h .TP .B \-\-help Show this help message and exit. .UNINDENT .INDENT 0.0 .TP .B \-V .TP .B \-\-version Show version string and exit. .UNINDENT .INDENT 0.0 .TP .B \-\-version\-check Run a version check and exit. .UNINDENT .INDENT 0.0 .TP .B \-\-auto\-version\-check {yes,true,1,on,no,false,0,off} Enable or disable the automatic check for a new version of Streamlink. .sp Default is: \fB\(dqno\(dq\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-\-plugins Print a list of all currently installed plugins. .UNINDENT .INDENT 0.0 .TP .B \-\-plugin\-dir DIRECTORY Load plugins from this directory. .sp Can be set multiple times to load plugins from multiple directories. .UNINDENT .INDENT 0.0 .TP .B \-\-plugin\-dirs DIRECTORY Load plugins from a list of comma\-separated directories. (deprecated) .UNINDENT .INDENT 0.0 .TP .B \-\-show\-matchers PLUGIN Show the list of matchers of a specific plugin (URL regex pattern with opt. priority and opt. name). .sp The output is a human\-readable pseudo YAML format. Please use \fI\%\-\-json\fP when reading matcher data programmatically. .UNINDENT .INDENT 0.0 .TP .B \-\-can\-handle\-url URL Check if Streamlink has a plugin that can handle the specified URL. .sp Status code is \fB0\fP on success, \fB1\fP on failure. .sp Useful for external scripting. .UNINDENT .INDENT 0.0 .TP .B \-\-can\-handle\-url\-no\-redirect URL Same as \fI\%\-\-can\-handle\-url\fP, but without following redirects when looking up the URL. .UNINDENT .INDENT 0.0 .TP .B \-\-config FILENAME Load options from this config file. .sp Can be repeated to load multiple files, in which case the options are merged on top of each other where the last config has highest priority. .UNINDENT .INDENT 0.0 .TP .B \-\-no\-config Disable loading any default or custom config files. .UNINDENT .INDENT 0.0 .TP .B \-\-locale LOCALE Override the system\(aqs locale setting, for selecting the preferred subtitle and audio language. .sp The locale is formatted as \fB[language_code]_[country_code]\fP, e.g. \fBen_US\fP or \fBes_ES\fP\&. .sp Default is: \fBsystem locale\fP\&. .UNINDENT .SS Logging arguments .INDENT 0.0 .TP .B \-l LEVEL .TP .B \-\-loglevel LEVEL Set the log message threshold. .sp Valid levels are, in order of increasing verbosity: .sp \fBnone\fP, \fBcritical\fP, \fBerror\fP, \fBwarning\fP, \fBinfo\fP, \fBdebug\fP, \fBtrace\fP, \fBall\fP .sp Default is: \fB\(dqinfo\(dq\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-\-logformat FORMAT Set a custom logging format. .sp See the Python standard library\(aqs \fBlogging.Formatter\fP docs for more information about the logging format and the available \fBLogRecord\fP attributes. Streamlink\(aqs formatter uses the curly brace style. .sp The default format depends on the chosen log level. .sp For verbose levels (\fBtrace\fP and \fBall\fP): .sp Default is: \fB\(dq[{asctime}][{name}][{levelname}] {message}\(dq\fP\&. .sp Otherwise: .sp Default is: \fB\(dq[{name}][{levelname}] {message}\(dq\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-\-logdateformat DATEFORMAT Set a custom logging date format. .sp This formats the \fBLogRecord\fP\(aqs \fBasctime\fP attribute via \fBstrftime()\fP\&. .sp The default date format depends on the chosen log level. .sp For verbose levels (\fBtrace\fP and \fBall\fP): .sp Default is: \fB\(dq%H:%M:%S.%f\(dq\fP\&. .sp Otherwise: .sp Default is: \fB\(dq%H:%M:%S\(dq\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-\-logfile FILE Append log output to \fBFILE\fP instead of writing to stdout/stderr. .sp User prompts and download progress won\(aqt be written to \fBFILE\fP\&. .sp A value of \fB\-\fP (dash) will set the file name to an ISO8601\-like string and will choose the following default log directories. .sp Windows: .INDENT 7.0 .INDENT 3.5 .sp .EX %TEMP%\estreamlink\elogs .EE .UNINDENT .UNINDENT .sp macOS: .INDENT 7.0 .INDENT 3.5 .sp .EX ${HOME}/Library/Logs/streamlink .EE .UNINDENT .UNINDENT .sp Linux/BSD: .INDENT 7.0 .INDENT 3.5 .sp .EX ${XDG_STATE_HOME:\-${HOME}/.local/state}/streamlink/logs .EE .UNINDENT .UNINDENT .UNINDENT .INDENT 0.0 .TP .B \-Q .TP .B \-\-quiet Suppress all console and log output, and also disable user prompts. .UNINDENT .INDENT 0.0 .TP .B \-j .TP .B \-\-json Output JSON representations instead of the normal text output. .sp Useful for external scripting. .UNINDENT .SS Network arguments .INDENT 0.0 .TP .B \-\-interface INTERFACE Set the network interface. .UNINDENT .INDENT 0.0 .TP .B \-4 .TP .B \-\-ipv4 Resolve address names to IPv4 only. This option overrides \fI\%\-\-ipv6\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-6 .TP .B \-\-ipv6 Resolve address names to IPv6 only. This option overrides \fI\%\-\-ipv4\fP\&. .UNINDENT .SS Player options .INDENT 0.0 .TP .B \-p PATH .TP .B \-\-player PATH Set the player executable that will be launched (unless a different output method was chosen). .sp Either set an absolute or relative path to the player executable, or just set the executable\(aqs name if it can be resolved from the paths of the system\(aqs \fBPATH\fP environment variable. .sp In addition to setting the player executable path, custom player arguments can be set via \fI\%\-\-player\-args\fP\&. .sp \fBNOTE:\fP .INDENT 7.0 .INDENT 3.5 In the past, \fI\%\-\-player\fP allowed defining additional player arguments, which as a consequence required wrapping player paths that contained spaces in quotation marks. This is unsupported since release \fB6.0.0\fP\&. .UNINDENT .UNINDENT .sp Default is: \fBVLC player, if available\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-a ARGUMENTS .TP .B \-\-player\-args ARGUMENTS Set a string of custom \fI\%\-\-player\fP launch arguments that will be parsed and tokenized. .sp The value can contain formatting variables surrounded by curly braces, \fB{\fP and \fB}\fP\&. Curly brace characters can be escaped by doubling, e.g. \fB{{\fP and \fB}}\fP\&. .sp Available formatting variables: .INDENT 7.0 .TP .B \fB{playerinput}\fP This is the input argument that the \fI\%\-\-player\fP will receive. For standard input (stdin), it is \fB\-\fP (dash), but it can also be a file path or URL, depending on the options used. If unset, then the player input argument will be appended to the parsed player arguments list. .TP .B \fB{playertitleargs}\fP The automatically generated player title arguments, if a supported \fI\%\-\-player\fP was found. See \fI\%\-\-title\fP for more. If unset, automatically generated player title arguments will be prepended to the parsed player arguments list. .UNINDENT .sp Example: .INDENT 7.0 .INDENT 3.5 .sp .EX streamlink \-p vlc \-a \(dq\-\-play\-and\-exit \-\-no\-one\-instance\(dq [stream] .EE .UNINDENT .UNINDENT .sp Default is: \fB\(dq\(dq\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-\-player\-env KEY=VALUE Add an additional environment variable to the spawned \fI\%\-\-player\fP process, in addition to the ones inherited from the Streamlink/Python parent process. This allows setting player environment variables in config files. .sp Can be repeated to add multiple environment variables. .UNINDENT .INDENT 0.0 .TP .B \-v .TP .B \-\-player\-verbose Write the \fI\%\-\-player\fP\(aqs stdout/stderr output to Streamlink\(aqs stdout/stderr output. .UNINDENT .INDENT 0.0 .TP .B \-\-verbose\-player Deprecated in favor of \fI\%\-\-player\-verbose\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-n .TP .B \-\-player\-fifo Make the \fI\%\-\-player\fP read the stream through a named pipe instead of the stdin pipe. .UNINDENT .INDENT 0.0 .TP .B \-\-fifo Deprecated in favor of \fI\%\-\-player\-fifo\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-\-player\-http Make the \fI\%\-\-player\fP read the stream through HTTP instead of the stdin pipe. .UNINDENT .INDENT 0.0 .TP .B \-\-player\-continuous\-http Make the \fI\%\-\-player\fP read the stream through HTTP, but unlike \fI\%\-\-player\-http\fP, it will continuously try to open the stream if the player requests it. .sp This enables the handling of stream disconnects if the player is capable of reconnecting to a HTTP stream. This is usually done by setting the player to a \(dqrepeat mode\(dq. .UNINDENT .INDENT 0.0 .TP .B \-\-player\-external\-http Serve stream data through HTTP without opening the \fI\%\-\-player\fP\&. This is useful to allow external devices like smartphones or streaming boxes to watch streams they wouldn\(aqt be able to otherwise. .sp The default behavior is similar to the \fI\%\-\-player\-continuous\-http\fP option, but no player program will be started, and the server will listen on all available connections instead of just in the local (loopback) interface. .sp See \fI\%\-\-player\-external\-http\-interface\fP for choosing a specific network interface, and see \fI\%\-\-player\-external\-http\-port\fP for choosing a non\-randomized port. .sp Optionally, the \fI\%\-\-player\-external\-http\-continuous\fP option allows for disabling the continuous run\-mode, so that Streamlink will stop when the stream ends. .sp The URLs that can be used to access the stream will be printed to the console, and the server can be interrupted using CTRL\-C. .UNINDENT .INDENT 0.0 .TP .B \-\-player\-external\-http\-continuous {yes,true,1,on,no,false,0,off} Set the run\-mode of \fI\%\-\-player\-external\-http\fP to continuous or non\-continuous. .sp In the continuous run\-mode, Streamlink will keep running after the stream has ended and will wait for the next HTTP request being made unless it gets shut down via CTRL\-C. .sp If set to non\-continuous, Streamlink will stop once the stream has ended. .sp Default is: \fBtrue\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-\-player\-external\-http\-interface INTERFACE Set the network interface on which the HTTP server will be listening on. If unset or set to \fB0.0.0.0\fP, all available interfaces will be bound. .UNINDENT .INDENT 0.0 .TP .B \-\-player\-external\-http\-port PORT Set the port of the external HTTP server if that mode is enabled. Omit or set to \fB0\fP to use a random high ( >1024) port. .UNINDENT .INDENT 0.0 .TP .B \-\-player\-passthrough TYPES A comma\-delimited list of stream types to pass to the \fI\%\-\-player\fP as a URL to let it handle the transport of the stream instead of Streamlink. .sp Stream types that can be converted into a playable URL are: .sp hls, http .sp Make sure the player can handle the stream type when using this. .UNINDENT .INDENT 0.0 .TP .B \-\-player\-no\-close By default, Streamlink will close the \fI\%\-\-player\fP when the stream ends. This is to avoid \(dqdead\(dq GUI players lingering after Streamlink has exited. .sp It does however have the side\-effect of sometimes closing a player before it has played back all of its cached data. .sp This option will instead let the player decide when to exit. .UNINDENT .INDENT 0.0 .TP .B \-t TITLE .TP .B \-\-title TITLE Change the title of the \fI\%\-\-player\fP\(aqs window. .sp Please see the \(dq\fI\%Metadata variables\fP\(dq section of Streamlink\(aqs CLI documentation for all available metadata variables, as well as the \(dq\fI\%Plugins\fP\(dq section for the list of metadata variables defined in each plugin. .sp Only the following players are supported: .sp mpv, potplayer, vlc .sp Example: .INDENT 7.0 .INDENT 3.5 .sp .EX streamlink \-p mpv \-\-title \(dq{author} \- {category} \- {title}\(dq [STREAM] .EE .UNINDENT .UNINDENT .UNINDENT .SS File output options .INDENT 0.0 .TP .B \-O .TP .B \-\-stdout Write stream data to \fBstdout\fP instead of playing it in the \fI\%\-\-player\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-o FILENAME .TP .B \-\-output FILENAME Write stream data to \fBFILENAME\fP instead of playing it in the \fI\%\-\-player\fP\&. If \fBFILENAME\fP is set to \fB\-\fP (dash), then the stream data will be written to \fBstdout\fP, similar to the \fI\%\-\-stdout\fP argument. .sp Directories and subdirectories will be created if they do not exist, if filesystem permissions allow. .sp Unless \fI\%\-\-force\fP is set, Streamlink will ask for confirmation before writing if \fBFILENAME\fP already exists. .sp Please see the \(dq\fI\%Metadata variables\fP\(dq section of Streamlink\(aqs CLI documentation for all available metadata variables, as well as the \(dq\fI\%Plugins\fP\(dq section for the list of metadata variables defined in each plugin. .sp Unsupported characters in substituted variables will be replaced with an underscore. .sp Example: .INDENT 7.0 .INDENT 3.5 .sp .EX streamlink \-\-output \(dq~/recordings/{author}/{category}/{id}\-{time:%Y%m%d%H%M%S}.ts\(dq [STREAM] .EE .UNINDENT .UNINDENT .UNINDENT .INDENT 0.0 .TP .B \-r FILENAME .TP .B \-\-record FILENAME Write stream data to \fBFILENAME\fP while at the same time allowing playback in the \fI\%\-\-player\fP or writing it to \fI\%\-\-stdout\fP\&. If \fBFILENAME\fP is set to \fB\-\fP (dash), then the stream data will be written to \fBstdout\fP, similar to the \fI\%\-\-stdout\fP argument, while still opening the player. .sp Directories and subdirectories will be created if they do not exist, if filesystem permissions allow. .sp Unless \fI\%\-\-force\fP is set, Streamlink will ask for confirmation before writing if \fBFILENAME\fP already exists. .sp Please see the \(dq\fI\%Metadata variables\fP\(dq section of Streamlink\(aqs CLI documentation for all available metadata variables, as well as the \(dq\fI\%Plugins\fP\(dq section for the list of metadata variables defined in each plugin. .sp Unsupported characters in substituted variables will be replaced with an underscore. .sp Example: .INDENT 7.0 .INDENT 3.5 .sp .EX streamlink \-\-record \(dq~/recordings/{author}/{category}/{id}\-{time:%Y%m%d%H%M%S}.ts\(dq [STREAM] .EE .UNINDENT .UNINDENT .UNINDENT .INDENT 0.0 .TP .B \-R FILENAME .TP .B \-\-record\-and\-pipe FILENAME Deprecated in favor of \fI\%\-\-stdout\fP \fI\%\-\-record=FILENAME\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-\-fs\-safe\-rules The rules used to make formatting variables filesystem\-safe are chosen automatically according to the type of system in use. This overrides the automatic detection. .sp Intended for use when Streamlink is running on a UNIX\-like OS but writing to Windows filesystems such as NTFS; USB devices using VFAT or exFAT; CIFS shares that are enforcing Windows filename limitations, etc. .sp These characters are replaced with an underscore for the rules in use: .INDENT 7.0 .IP \(bu 2 POSIX: \fB\ex00\-\ex1F /\fP .IP \(bu 2 Windows: \fB\ex00\-\ex1F \ex7F \(dq * / : < > ? \e |\fP .UNINDENT .UNINDENT .INDENT 0.0 .TP .B \-f .TP .B \-\-force When using \fI\%\-\-output\fP or \fI\%\-\-record\fP, always write to file even if it already exists (overwrite). .UNINDENT .INDENT 0.0 .TP .B \-\-progress {yes,force,no} When using \fI\%\-\-output\fP or \fI\%\-\-record\fP, show or hide the download progress bar, or force it if there\(aqs no terminal. .sp Default is: \fByes\fP\&. .UNINDENT .SS Stream options .INDENT 0.0 .TP .B \-\-url URL A URL to attempt to extract streams from. .sp Usually, the protocol of http(s) URLs can be omitted (\fBhttps://\fP), depending on the implementation of the plugin being used. .sp This is an alternative to setting the URL using a positional argument and can be useful if set in a config file. .UNINDENT .INDENT 0.0 .TP .B \-\-default\-stream STREAM Stream to play. .sp Use \fBbest\fP or \fBworst\fP for selecting the highest or lowest available quality. .sp Fallback streams can be specified by using a comma\-separated list: .INDENT 7.0 .INDENT 3.5 .sp .EX \(dq720p,480p,best\(dq .EE .UNINDENT .UNINDENT .sp This is an alternative to setting the stream using a positional argument and can be useful if set in a config file. .UNINDENT .INDENT 0.0 .TP .B \-\-stream\-url If possible, translate the resolved stream to a URL and print it. .UNINDENT .INDENT 0.0 .TP .B \-\-retry\-streams DELAY Retry fetching the list of available streams until streams are found while waiting \fBDELAY\fP second(s) between each attempt. .sp The number of retry attempts can be capped with \fI\%\-\-retry\-max\fP\&. A default value of \fB1\fP is implied for non\-zero values of \fI\%\-\-retry\-max\fP\&. .sp If both \fI\%\-\-retry\-streams\fP and \fI\%\-\-retry\-max\fP are set to \fB0\fP, then only one attempt will be made to fetch the list of available streams. This is the default behavior. .UNINDENT .INDENT 0.0 .TP .B \-\-retry\-max COUNT Stop fetching the list of available streams after \fBCOUNT\fP retry attempt(s). .sp A value of \fB0\fP makes Streamlink fetch streams indefinitely if \fI\%\-\-retry\-streams\fP is set to a non\-zero value. If \fI\%\-\-retry\-streams\fP is unset, then the default delay between fetching available streams is 1 second. .UNINDENT .INDENT 0.0 .TP .B \-\-retry\-open ATTEMPTS After a successful fetch, try \fBATTEMPTS\fP time(s) to open the stream until giving up. .sp Default is: \fB1\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-\-stream\-types TYPES .TP .B \-\-stream\-priority TYPES A comma\-delimited list of stream types to allow. .sp The order will be used to separate streams when there are multiple streams with the same name but different stream types. Any stream type not listed will be omitted from the available streams list. An \fB*\fP (asterisk) can be used as a wildcard to match any other type of stream, e.g. dash. .sp Default is: \fB\(dqhls,http,*\(dq\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-\-stream\-sorting\-excludes STREAMS Fine\-tune the \fBbest\fP and \fBworst\fP stream name synonyms by excluding unwanted streams. .sp If all of the available streams get excluded, \fBbest\fP and \fBworst\fP will become inaccessible and new special stream synonyms \fBbest\-unfiltered\fP and \fBworst\-unfiltered\fP can be used as a fallback selection method. .sp The filter\-expression\(aqs format is: .INDENT 7.0 .INDENT 3.5 .sp .EX [operator] .EE .UNINDENT .UNINDENT .sp Valid operators are \fB>\fP, \fB>=\fP, \fB<\fP and \fB<=\fP\&. If no operator is specified then equality is tested. .sp For example this will exclude streams ranked higher than \(dq480p\(dq: .INDENT 7.0 .INDENT 3.5 .sp .EX \-\-stream\-sorting\-excludes \(dq>480p\(dq .EE .UNINDENT .UNINDENT .sp Multiple filters can be used by separating each expression with a comma. .sp For example this will exclude streams from two quality types: .INDENT 7.0 .INDENT 3.5 .sp .EX \-\-stream\-sorting\-excludes \(dq>480p,>medium\(dq .EE .UNINDENT .UNINDENT .UNINDENT .SS Stream transport options .INDENT 0.0 .TP .B \-\-ringbuffer\-size SIZE The maximum size of the ringbuffer. .sp Mebibytes or kibibytes (base 2) can be specified via the M or K suffix respectively. .sp The ringbuffer is used as a temporary storage between the stream and the player. This allows Streamlink to download the stream faster than the player which reads the data from the ringbuffer. .sp The smaller the size of the ringbuffer, the higher the chance of the player buffering if the download speed decreases, and the higher the size, the more data can be use as a storage to recover from volatile download speeds. .sp Most players have their own additional cache and will read the ringbuffer\(aqs content as soon as data is available. If the player stops reading data while playback is paused, Streamlink will continue to download the stream in the background as long as the ringbuffer doesn\(aqt get full. .sp Default is: \fB\(dq16M\(dq\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-\-stream\-segment\-attempts ATTEMPTS The number of download attempts of each stream segment before giving up. .sp This applies to all different kinds of segmented stream types, such as DASH, HLS, etc. .sp Default is: \fB3\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-\-stream\-segment\-threads THREADS The size of the thread pool used to download segments. Minimum value is \fB1\fP and maximum is \fB10\fP\&. .sp This applies to all different kinds of segmented stream types, such as DASH, HLS, etc. .sp Default is: \fB1\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-\-stream\-segment\-timeout TIMEOUT The maximum time to wait for each segment to start downloading. .sp This applies to all different kinds of segmented stream types, such as DASH, HLS, etc. .sp Default is: \fB10.0\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-\-stream\-timeout TIMEOUT The maximum time to wait for an unfiltered stream to continue outputting data. .sp This applies to all different kinds of stream types, such as DASH, HLS, HTTP, etc. .sp Default is: \fB60.0\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-\-mux\-subtitles Automatically mux available subtitles into the output stream. .sp Needs to be supported by the used plugin. .UNINDENT .SS HLS options .INDENT 0.0 .TP .B \-\-hls\-live\-edge SEGMENTS Number of segments from the live stream\(aqs current live position to begin streaming. The size or length of each segment is determined by the streaming provider. .sp Lower values will decrease the latency, but will also increase the chance of buffering, as there is less time for Streamlink to download segments and write their data to the output buffer. The number of parallel segment downloads can be set with \fI\%\-\-stream\-segment\-threads\fP and the HLS playlist reload time to fetch and queue new segments can be overridden with \fI\%\-\-hls\-playlist\-reload\-time\fP\&. .sp Default is: \fB3\fP\&. .sp \fBNOTE:\fP .INDENT 7.0 .INDENT 3.5 During live playback, the caching/buffering settings of the used player will add additional latency. To adjust this, please refer to the player\(aqs own documentation for the required configuration. Player parameters can be set via \fI\%\-\-player\-args\fP\&. .UNINDENT .UNINDENT .UNINDENT .INDENT 0.0 .TP .B \-\-hls\-segment\-stream\-data Immediately write segment data into output buffer while downloading. .UNINDENT .INDENT 0.0 .TP .B \-\-hls\-playlist\-reload\-attempts ATTEMPTS The maximum number of attempts when reloading the HLS playlist before giving up. .sp Default is: \fB3\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-\-hls\-playlist\-reload\-time TIME Set a custom HLS playlist reload time value, either in seconds or by using one of the following keywords: .INDENT 7.0 .IP \(bu 2 segment: The duration of the last segment in the current playlist .IP \(bu 2 live\-edge: The sum of segment durations of the live edge value minus one .IP \(bu 2 default: The playlist\(aqs target duration metadata .UNINDENT .sp Default is: \fBdefault\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-\-hls\-segment\-queue\-threshold FACTOR The multiplication factor of the HLS playlist\(aqs target duration after which the stream will be stopped early if no new segments were queued after refreshing the playlist (multiple times). The target duration defines the maximum duration a single segment can have, meaning new segments must be available during this time frame, otherwise playback issues can occur. .sp The intention of this queue threshold is to be able to stop early when the end of a stream doesn\(aqt get announced by the server, so Streamlink doesn\(aqt have to wait until a read\-timeout occurs. See \fI\%\-\-stream\-timeout\fP\&. .sp Set to \fB0\fP to disable. .sp Default is: \fB3\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-\-hls\-segment\-ignore\-names NAMES A comma\-delimited list of segment names that will get filtered out. .sp Example: \fB\-\-hls\-segment\-ignore\-names 000,001,002\fP .sp This will ignore every segment that ends with 000.ts, 001.ts and 002.ts .sp Default is: \fBNone\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-\-hls\-segment\-key\-uri URI Override the segment encryption key URIs for encrypted streams. .sp The value can be templated using the following variables, which will be replaced with their respective part from the source segment URI: .INDENT 7.0 .INDENT 3.5 .sp .EX {url} {scheme} {netloc} {path} {query} .EE .UNINDENT .UNINDENT .sp Examples: .INDENT 7.0 .INDENT 3.5 .sp .EX \-\-hls\-segment\-key\-uri \(dqhttps://example.com/hls/encryption_key\(dq \-\-hls\-segment\-key\-uri \(dq{scheme}://1.2.3.4{path}{query}\(dq \-\-hls\-segment\-key\-uri \(dq{scheme}://{netloc}/custom/path/to/key\(dq .EE .UNINDENT .UNINDENT .sp Default is: \fBNone\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-\-hls\-audio\-select CODE Select one or more specific audio sources by language code or name. Can be set to \fB*\fP (asterisk) to include all audio sources. .sp Examples: .INDENT 7.0 .INDENT 3.5 .sp .EX \-\-hls\-audio\-select \(dqEnglish,German\(dq \-\-hls\-audio\-select \(dqen,de\(dq \-\-hls\-audio\-select \(dq*\(dq .EE .UNINDENT .UNINDENT .sp \fBNOTE:\fP .INDENT 7.0 .INDENT 3.5 This is only useful in special circumstances where the regular locale option fails, such as when multiple sources of the same language exist. .UNINDENT .UNINDENT .UNINDENT .INDENT 0.0 .TP .B \-\-hls\-start\-offset [[XX:]XX:]XX[.XX] | [XXh][XXm][XX[.XX]s] The amount of time to skip from the beginning of the stream. For live streams, this is a negative offset from the end of the stream (rewind). .sp Default is: \fB0\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-\-hls\-duration [[XX:]XX:]XX[.XX] | [XXh][XXm][XX[.XX]s] Limit the playback duration, useful for watching segments of a stream. The actual duration may be slightly longer, as it is rounded to the nearest HLS segment. .sp Default is: \fBunlimited\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-\-hls\-live\-restart Skip to the beginning of a live stream, or as far back as possible. .UNINDENT .SS DASH options .INDENT 0.0 .TP .B \-\-dash\-manifest\-reload\-attempts ATTEMPTS The maximum number of attempts when reloading the DASH manifest before giving up. .sp Default is: \fB3\fP\&. .UNINDENT .SS FFmpeg options .INDENT 0.0 .TP .B \-\-ffmpeg\-ffmpeg FILENAME Set the location of the FFmpeg executable if it can\(aqt be resolved from the paths of the system\(aqs \fBPATH\fP environment variable. .sp FFmpeg is required to access or mux separate video and audio streams, e.g. in DASH streams or HLS streams with multiple sources. .sp Example: \fB\-\-ffmpeg\-ffmpeg \(dq/usr/local/bin/ffmpeg\(dq\fP .UNINDENT .INDENT 0.0 .TP .B \-\-ffmpeg\-no\-validation Disable FFmpeg validation and version logging. .UNINDENT .INDENT 0.0 .TP .B \-\-ffmpeg\-verbose Write FFmpeg\(aqs stderr output to Streamlink\(aqs stderr output. .UNINDENT .INDENT 0.0 .TP .B \-\-ffmpeg\-verbose\-path PATH Write FFmpeg\(aqs stderr output to PATH. .UNINDENT .INDENT 0.0 .TP .B \-\-ffmpeg\-loglevel LOGLEVEL Change FFmpeg\(aqs \fB\-loglevel\fP value to \fBLOGLEVEL\fP\&. .sp Unless \fI\%\-\-ffmpeg\-verbose\fP or \fI\%\-\-ffmpeg\-verbose\-path\fP is set, changing the log level won\(aqt have any effect. .sp Default is: \fB\(dqinfo\(dq\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-\-ffmpeg\-fout OUTFORMAT Set the output format to \fBOUTFORMAT\fP\&. This only applies to streams which require muxing. .sp Default is: \fB\(dqmatroska\(dq\fP\&. .sp Example: \fB\-\-ffmpeg\-fout \(dqmpegts\(dq\fP .UNINDENT .INDENT 0.0 .TP .B \-\-ffmpeg\-video\-transcode CODEC Transcode the video to \fBCODEC\fP\&. This only applies to streams which require muxing. .sp Default is: \fB\(dqcopy\(dq\fP\&. .sp Example: \fB\-\-ffmpeg\-video\-transcode \(dqh264\(dq\fP .UNINDENT .INDENT 0.0 .TP .B \-\-ffmpeg\-audio\-transcode CODEC Transcode the audio to \fBCODEC\fP\&. This only applies to streams which require muxing. .sp Default is: \fB\(dqcopy\(dq\fP\&. .sp Example: \fB\-\-ffmpeg\-audio\-transcode \(dqaac\(dq\fP .UNINDENT .INDENT 0.0 .TP .B \-\-ffmpeg\-copyts Set the \fB\-copyts\fP FFmpeg option, so input timestamps won\(aqt be processed and the initial start time offset value be kept. .UNINDENT .INDENT 0.0 .TP .B \-\-ffmpeg\-start\-at\-zero Enable the \fB\-start_at_zero\fP FFmpeg option when using \fI\%\-\-ffmpeg\-copyts\fP\&. .UNINDENT .SS HTTP options .INDENT 0.0 .TP .B \-\-http\-proxy HTTP_PROXY An HTTP proxy to use for all HTTP and HTTPS requests, including WebSocket connections. .sp Example: \fB\-\-http\-proxy \(dqhttp://hostname:port/\(dq\fP .UNINDENT .INDENT 0.0 .TP .B \-\-http\-cookie KEY=VALUE A cookie to add to each HTTP request. .sp Can be repeated to add multiple cookies. .UNINDENT .INDENT 0.0 .TP .B \-\-http\-header KEY=VALUE A header to add to each HTTP request. .sp Can be repeated to add multiple headers. .UNINDENT .INDENT 0.0 .TP .B \-\-http\-query\-param KEY=VALUE A query parameter to add to each HTTP request. .sp Can be repeated to add multiple query parameters. .UNINDENT .INDENT 0.0 .TP .B \-\-http\-ignore\-env Ignore HTTP settings set in the environment, such as environment variables (\fBHTTP_PROXY\fP, etc) or \fB~/.netrc\fP authentication. .UNINDENT .INDENT 0.0 .TP .B \-\-http\-no\-ssl\-verify Don\(aqt attempt to verify TLS/SSL certificates. .sp Use with caution, as it has TLS/SSL security implications. .UNINDENT .INDENT 0.0 .TP .B \-\-http\-disable\-dh Disable Diffie Hellman key exchange. .sp Use with caution, as it has TLS/SSL security implications. .UNINDENT .INDENT 0.0 .TP .B \-\-http\-ssl\-cert PEM_FILENAME SSL certificate to use: a .pem file. .UNINDENT .INDENT 0.0 .TP .B \-\-http\-ssl\-cert\-crt\-key CRT_FILENAME KEY_FILENAME SSL certificate to use: a .crt and a .key file. .UNINDENT .INDENT 0.0 .TP .B \-\-http\-timeout TIMEOUT Set the general timeout value used by all HTTP requests except the ones covered by other options. .sp Default is: \fB20.0\fP\&. .UNINDENT .SS Web browser options .INDENT 0.0 .TP .B \-\-webbrowser {yes,true,1,on,no,false,0,off} Enable or disable support for Streamlink\(aqs webbrowser API. .sp Streamlink\(aqs webbrowser API allows plugins which implement it to launch a web browser and extract data from websites which they otherwise couldn\(aqt do via the regular HTTP session in Python due to specific JavaScript restrictions. .sp The web browser is run isolated and in a clean environment without access to regular user data. .sp Streamlink currently only supports Chromium\-based web browsers using the Chrome Devtools Protocol (CDP). This includes Chromium itself, Google Chrome, Microsoft Edge, Brave, Vivaldi, and others, but full support for third party Chromium forks is not guaranteed. Please try Chromium or Google Chrome when encountering any issues. .sp Default is: \fBtrue\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-\-webbrowser\-executable PATH Path to the web browser\(aqs executable. .sp By default, it is looked up automatically according to the rules of the used webbrowser API implementation. This usually involves a list of known executable names and fallback paths on all supported operating systems. .UNINDENT .INDENT 0.0 .TP .B \-\-webbrowser\-timeout TIME The maximum amount of time which the web browser can take to launch and execute. .UNINDENT .INDENT 0.0 .TP .B \-\-webbrowser\-cdp\-host HOST Host for the web browser\(aqs inter\-process communication interface (CDP specific). .sp Default is: \fB127.0.0.1\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-\-webbrowser\-cdp\-port PORT Port for the web browser\(aqs inter\-process communication interface (CDP specific). .sp Tries to find a free port by default. .UNINDENT .INDENT 0.0 .TP .B \-\-webbrowser\-cdp\-timeout TIME The maximum amount of time for waiting on a single CDP command response. .UNINDENT .INDENT 0.0 .TP .B \-\-webbrowser\-headless {yes,true,1,on,no,false,0,off} Whether to launch the web browser in headless mode or not. When enabled, it stays completely hidden and doesn\(aqt require a desktop environment to run. .sp Please be aware that headless mode might be blocked by websites which implement bot detections. .sp Default is: \fBfalse\fP\&. .UNINDENT .SS Plugin options .SS Bbciplayer .INDENT 0.0 .TP .B \-\-bbciplayer\-username USERNAME The username used to register with bbc.co.uk. .UNINDENT .INDENT 0.0 .TP .B \-\-bbciplayer\-password PASSWORD A bbc.co.uk account password to use with \fI\%\-\-bbciplayer\-username\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-\-bbciplayer\-hd Prefer HD streams over local SD streams, some live programmes may not be broadcast in HD. .UNINDENT .SS Clubbingtv .INDENT 0.0 .TP .B \-\-clubbingtv\-username The username used to register with Clubbing TV. .UNINDENT .INDENT 0.0 .TP .B \-\-clubbingtv\-password A Clubbing TV account password to use with \fI\%\-\-clubbingtv\-username\fP\&. .UNINDENT .SS Nicolive .INDENT 0.0 .TP .B \-\-niconico\-email EMAIL The email or phone number associated with your Niconico account .UNINDENT .INDENT 0.0 .TP .B \-\-niconico\-password PASSWORD The password of your Niconico account .UNINDENT .INDENT 0.0 .TP .B \-\-niconico\-user\-session VALUE Value of the user\-session token. .sp Can be used as an alternative to providing a password. .UNINDENT .INDENT 0.0 .TP .B \-\-niconico\-purge\-credentials Purge cached Niconico credentials to initiate a new session and reauthenticate. .UNINDENT .INDENT 0.0 .TP .B \-\-niconico\-timeshift\-offset [[XX:]XX:]XX | [XXh][XXm][XXs] Amount of time to skip from the beginning of a stream. .sp Default is: \fB0\fP\&. .UNINDENT .SS Openrectv .INDENT 0.0 .TP .B \-\-openrectv\-email EMAIL The email associated with your openrectv account, required to access any openrectv stream. .UNINDENT .INDENT 0.0 .TP .B \-\-openrectv\-password PASSWORD An openrectv account password to use with \fI\%\-\-openrectv\-email\fP\&. .UNINDENT .SS Pixiv .INDENT 0.0 .TP .B \-\-pixiv\-sessionid SESSIONID The pixiv.net sessionid that\(aqs used in pixiv\(aqs PHPSESSID cookie. .UNINDENT .INDENT 0.0 .TP .B \-\-pixiv\-devicetoken DEVICETOKEN The pixiv.net device token that\(aqs used in pixiv\(aqs device_token cookie. .UNINDENT .INDENT 0.0 .TP .B \-\-pixiv\-purge\-credentials Purge cached Pixiv credentials to initiate a new session and reauthenticate. .UNINDENT .INDENT 0.0 .TP .B \-\-pixiv\-performer USER Select a co\-host stream instead of the owner stream. .UNINDENT .SS Raiplay .INDENT 0.0 .TP .B \-\-raiplay\-email EMAIL The email used to register with raiplay.it. .UNINDENT .INDENT 0.0 .TP .B \-\-raiplay\-password PASSWORD A raiplay.it account password to use with \fI\%\-\-raiplay\-email\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-\-raiplay\-purge\-credentials Purge cached RaiPlay credentials to initiate a new session and reauthenticate. .UNINDENT .SS Soop .INDENT 0.0 .TP .B \-\-soop\-username USERNAME The username used to register with sooplive.co.kr. .UNINDENT .INDENT 0.0 .TP .B \-\-soop\-password PASSWORD A sooplive.co.kr account password to use with \fI\%\-\-soop\-username\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-\-soop\-purge\-credentials Purge cached Soop credentials to initiate a new session and reauthenticate. .UNINDENT .INDENT 0.0 .TP .B \-\-soop\-stream\-password STREAM_PASSWORD The password for the stream. .UNINDENT .SS Steam .INDENT 0.0 .TP .B \-\-steam\-email EMAIL A Steam account email address to access friends/private streams .UNINDENT .INDENT 0.0 .TP .B \-\-steam\-password PASSWORD A Steam account password to use with \fI\%\-\-steam\-email\fP\&. .UNINDENT .SS Streann .INDENT 0.0 .TP .B \-\-streann\-url URL Source URL where the iframe is located, only required for direct URLs of ott.streann.com .UNINDENT .SS Tf1 .INDENT 0.0 .TP .B \-\-tf1\-email EMAIL The email address used to register with tf1.fr. .UNINDENT .INDENT 0.0 .TP .B \-\-tf1\-password PASSWORD A tf1.fr account password to use with \fI\%\-\-tf1\-email\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-\-tf1\-purge\-credentials Purge cached tf1.fr credentials to initiate a new session and reauthenticate. .UNINDENT .SS Twitcasting .INDENT 0.0 .TP .B \-\-twitcasting\-password PASSWORD Password for private Twitcasting streams. .UNINDENT .SS Twitch .INDENT 0.0 .TP .B \-\-twitch\-disable\-ads Skip embedded advertisement segments at the beginning or during a stream. Will cause these segments to be missing from the output. .UNINDENT .INDENT 0.0 .TP .B \-\-twitch\-low\-latency Enables low latency streaming by prefetching HLS segments. Sets \fI\%\-\-hls\-segment\-stream\-data\fP to true and \fI\%\-\-hls\-live\-edge\fP to 2, if it is higher. Reducing \fI\%\-\-hls\-live\-edge\fP to \fB1\fP will result in the lowest latency possible, but will most likely cause buffering. .sp In order to achieve true low latency streaming during playback, the player\(aqs caching/buffering settings will need to be adjusted and reduced to a value as low as possible, but still high enough to not cause any buffering. This depends on the stream\(aqs bitrate and the quality of the connection to Twitch\(aqs servers. Please refer to the player\(aqs own documentation for the required configuration. Player parameters can be set via \fI\%\-\-player\-args\fP\&. .sp \fBNOTE:\fP .INDENT 7.0 .INDENT 3.5 Low latency streams have to be enabled by the broadcasters on Twitch themselves. Regular streams can cause buffering issues with this option enabled due to the reduced \fI\%\-\-hls\-live\-edge\fP value. .UNINDENT .UNINDENT .UNINDENT .INDENT 0.0 .TP .B \-\-twitch\-api\-header KEY=VALUE A header to add to each Twitch API HTTP request. .sp Can be repeated to add multiple headers. .sp Useful for adding authentication data that can prevent ads. See the plugin\-specific documentation for more information. .UNINDENT .INDENT 0.0 .TP .B \-\-twitch\-access\-token\-param KEY=VALUE A parameter to add to the API request for acquiring the streaming access token. .sp Can be repeated to add multiple parameters. .UNINDENT .INDENT 0.0 .TP .B \-\-twitch\-force\-client\-integrity Don\(aqt attempt requesting the streaming access token without a client\-integrity token. .UNINDENT .INDENT 0.0 .TP .B \-\-twitch\-purge\-client\-integrity Purge cached Twitch client\-integrity token and acquire a new one. .UNINDENT .SS Ustreamtv .INDENT 0.0 .TP .B \-\-ustream\-password PASSWORD A password to access password protected UStream.tv channels. .UNINDENT .SS Ustvnow .INDENT 0.0 .TP .B \-\-ustvnow\-username USERNAME Your USTV Now account username .UNINDENT .INDENT 0.0 .TP .B \-\-ustvnow\-password PASSWORD Your USTV Now account password .UNINDENT .SS Wwenetwork .INDENT 0.0 .TP .B \-\-wwenetwork\-email EMAIL The email associated with your WWE Network account, required to access any WWE Network stream. .UNINDENT .INDENT 0.0 .TP .B \-\-wwenetwork\-password PASSWORD A WWE Network account password to use with \fI\%\-\-wwenetwork\-email\fP\&. .UNINDENT .SS Yupptv .INDENT 0.0 .TP .B \-\-yupptv\-boxid BOXID The yupptv.com boxid that\(aqs used in the BoxId cookie. .UNINDENT .INDENT 0.0 .TP .B \-\-yupptv\-yuppflixtoken YUPPFLIXTOKEN The yupptv.com yuppflixtoken that\(aqs used in the YuppflixToken cookie. .UNINDENT .INDENT 0.0 .TP .B \-\-yupptv\-purge\-credentials Purge cached YuppTV credentials to initiate a new session and reauthenticate. .UNINDENT .SS Zattoo .INDENT 0.0 .TP .B \-\-zattoo\-email EMAIL The email associated with your zattoo account, required to access any zattoo stream. .UNINDENT .INDENT 0.0 .TP .B \-\-zattoo\-password PASSWORD A zattoo account password to use with \fI\%\-\-zattoo\-email\fP\&. .UNINDENT .INDENT 0.0 .TP .B \-\-zattoo\-purge\-credentials Purge cached zattoo credentials to initiate a new session and reauthenticate. .UNINDENT .INDENT 0.0 .TP .B \-\-zattoo\-stream\-types TYPES A comma\-delimited list of stream types which should be used. .sp The following types are allowed: dash, hls7 .sp Default is: \fB\(dqdash\(dq\fP\&. .UNINDENT .SH BUGS .sp Please open a new issue on Streamlink\(aqs issue tracker on GitHub and use the appropriate issue forms: .sp \X'tty: link https://github.com/streamlink/streamlink/issues'\fI\%https://github.com/streamlink/streamlink/issues\fP\X'tty: link' .SH SEE ALSO .sp For more detailed information about config files, plugin sideloading, streaming protocols, proxy support, metadata, or plugin specific stuff, please see Streamlink\(aqs online CLI documentation here: .sp \X'tty: link https://streamlink.github.io/cli.html'\fI\%https://streamlink.github.io/cli.html\fP\X'tty: link' .sp The list of available plugins and their descriptions can be found here: .sp \X'tty: link https://streamlink.github.io/plugins.html'\fI\%https://streamlink.github.io/plugins.html\fP\X'tty: link' .SH AUTHOR Streamlink Contributors .SH COPYRIGHT 2025, Streamlink .\" Generated by docutils manpage writer. . ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/_man.rst0000644000175100001660000000256615003227510015675 0ustar00runnerdocker:orphan: Streamlink ========== Synopsis -------- .. code-block:: console streamlink [OPTIONS] [STREAM] Examples -------- .. code-block:: console streamlink --loglevel debug youtu.be/VIDEO-ID best streamlink --player mpv --player-args '--no-border --no-keepaspect-window' twitch.tv/CHANNEL 1080p60 streamlink --player-external-http --player-external-http-port 8888 URL STREAM streamlink --output /path/to/file --http-timeout 60 URL STREAM streamlink --stdout URL STREAM | ffmpeg -i pipe:0 ... streamlink --http-header 'Authorization=OAuth TOKEN' --http-header 'Referer=URL' URL STREAM streamlink --hls-live-edge 5 --stream-segment-threads 5 'hls://https://host/playlist.m3u8' best streamlink --twitch-low-latency -p mpv -a '--cache=yes --demuxer-max-back-bytes=2G' twitch.tv/CHANNEL best Options ------- .. argparse:: Bugs ---- Please open a new issue on Streamlink's issue tracker on GitHub and use the appropriate issue forms: https://github.com/streamlink/streamlink/issues See also -------- For more detailed information about config files, plugin sideloading, streaming protocols, proxy support, metadata, or plugin specific stuff, please see Streamlink's online CLI documentation here: https://streamlink.github.io/cli.html The list of available plugins and their descriptions can be found here: https://streamlink.github.io/plugins.html ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745694559.9428184 streamlink-7.3.0/docs/_static/0000755000175100001660000000000015003227540015651 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/_static/apple-touch-icon.png0000644000175100001660000002747415003227510021541 0ustar00runnerdocker‰PNG  IHDR´´=Í2tEXtSoftwareAdobe ImageReadyqÉe<.ÞIDATxÚì} œ\GyçWõ®î™žûÔIsj4:,ÉF¶e°ÁØ6Ák'!‹¬MìrÄæJh‰6aaÁÎrgˆe!K±å [²„e]#Íh.Í¥94÷ÝÝïªÚªz#[ÆšyÝÓÝ3=3¯~¿±dIÝýú½}õÿ®ÿ‡(¥à-o­•…½[à-ÐÞò–hoy+ùKŽçÅ!ﺬ{ƒ‡TFζ0²{~úÑ·Œ{wÄ}Åãס¸^ìzÁu øŒ¯ß0ëf?Àþ÷‹Tz:CÆßJ·ÌßÜ '&ƒÁ ñî’èU±nüÂãåaüþqý©NP1»ÃŠ„(Q1š(TÈK9ÿ~úÙƒ-ïny€NÍ âm°/Ûíî¡0ùÄŒM·Ú]×GÑ0LdHô°‚Ñ—¥ðØÅ¾¯Üön èT¢^b½aÂ@6c¡ý6…,·»Ê`¸œ&‘Ÿç*ðÝΖ‰føé}¶w7=@¯ QâÇü7•ôO)ÿ0b {tŠ´˜ï!ûñaèËUÉ—¥ï[ÝÁ÷D<@{€^vz±Ãz}õ÷…,ò@ÈF¥ÌÓâyK Aˆû¥tD¾›…~Õò©;Æ<@{€NúÚüyöŒx×>&PÍx²êòtéÌè* PÉ=J*#:†ÑÑlÙþrïh×sðèLР¾¶W`;üšÍPè÷ ”¹#gƒ0éÚôÂôâý(³…U·Zô &ÚËÎvÅõ0:¡]j_s#àPHpçØÂ˜ùÞ¹¬Â ® æŒ;œ£Ð_ÈÖ盥;/®5â:ÎUüešbª»fˆôÞY ÝcÈv\:(ƒ}Œ'_y„ñd;¾œñ§Q¾ŒÊ:°sr]#"X8ŽÐŸ!ÿdiô‡0f\êøwë ×9 ·~úWãDýoc&þ#É|v;G! ƒïÂY‡‡ ÄмãÙR Fuô ×—`D ?vŽßòã»ß=âz½:Hñf㩃ÒwLYøÏC¶º&F˜Æ¡Ya‘µÎÀáPò®ÝW‹Yiá8–”Gů%F†LNæ¨øŸÒ°ÿ©¦ÏÞ<±ZGÐ1¬ÚfL)¡·Ì2 ‡-Á“®uá9áðù.µƒ43-À½,VQÁÊ˽¦̲®4„ǯe“™2y>O¥_Ë˼pü£·„=@¯A@ïyä%e¨k¦"Lí‡'mx»kä‚?ËùJøÏŸi| Yß‹* ¥eiØv£÷»TLçrø‘é_È)8õè^ÓôZt0ˆKí}ÛçLù]îÓ *¡.ôñx2sôxôB¸ 8’I:v¯í@›ÁØTÅ€ã lö·fš]™ü¨@%?>ÿÙ7·®âzµå¡CYœógãzŸN ‚Zœ'³[#!ð5µû’äT“y˜v¤¶ž9Žõ qy Œ_s`·oÐèÿ,ÁÒ÷Ÿ œõ½zŠ?y8ß ÚƒÀB6ÝOÀ¥¬“×]0‡Oéë_ÛE¦§D4#¥:²_ί7”:Ž£Ë’0 eËðt¦DþY³ñ±–‡ïw¶±è”t}ð:eæìž²ðƒ!R§Ð~ñº Ë¥¿|­MO6WWqÑ4°Š6`ó_K®Ž£†éx† Oækô«&N7ï3<@§ Ÿ‘[Âzm)ï Ûð^ÝIŒ WžÌ¬v´€ÖÛÈ0`5/âóQY úæêhGš.Ãh&†o§K䥲ÜΨˆåz…éEɧS6cYbúN“ :×t5%€g¦EIçËukEØÎÌ}S%èUu@}þ¨â×~L›sú“\EþñÙàÁž•¦!ëÐEÿýñt2&Ý&èïC„n' 4¤¾êzP»;ÀþôÒê.VËâŽcf¦¨1Ë*¢j,Àˆ’ls4ødzºý|ã_Þ9çz9,rð—þHÈ«ŽÐº w[®/Òu¯ôƒïR+ÈÃWVO^20TÌâÐáüš'j\h¨fýu¶†¾Sœ®=öЭ³& “°J>ùx]ˆJÍZø?Ø”0‡ÏÅ*³ï&ê“yn ÏòzS[åõ! ØFièÛv8ñk×úkJŒ¦ r8W%7ï¼à:QëÞCRvM^±Ñ;t›ü™AP%uÓä#Ä©»¸ÔƸr+ûýx‹Ý–ôDªëÀØR ÄŸîî8²½.Ñî}'MÆ?ñ ´õœZ†Ç5 è²ü:wN•ïÛðaâ ÔïzM†îÔ]´·žžŠ»¬sÍÑI’•í{S£%îÊ P= £–…~3S¶Î,ö°èè¿‚à³ZŽN÷è–ý)à7ÙÑÚ[HÃàontx²dW`[…E®ß va±kã._ ­`Ïhäa5Pt¬ñã;BÉH¥¯@SŠò>óô!ý©Aèï Enu¢ýib\”uªý½€ÃáõÇ“ãá×>?˜¥åNãnNžúâ/¡DÃh,S"Oähø; Ï?›háÉ5è¬O<Ÿ£`ý#“¼Ï¢¨\+¸U6@k:Z[ HO..~íó^Y‘†Ý¢º/ŠÆ]ða:¾Á~R¤ÂßÿÌí øõê4³È.Š€z·AÑ{ØÏMìjÜË:Ã!QÇ“#òبG/HCìì‘”áõ×$Í5"*¤Ò%hbTä¹ üâ<ÜÞoãêtð9wÎ|ãÂ7mt3”.ø³ ½9Onjtô.Vyº:eÍ,´•›zí6FG62~âDÒ0m,ö¡¯“|ýPLJ—Þ߸ªÍ ˆúgò·G0ý‹Â»mê^wÁ;D¤™)P;Û@»Ô.‰¼µ ÀæñëŠ-Bñ‰dæDlÓPžDcàþfÀ 'ÿ1öŒãêt0ˆ çnÛ̾Ý:E¿oSRãžaGoºÔ*ê“åUPÖ¹GÑXP¾ î8fdFï©C¦l?ž/ãG $éL,…O© hÆ“s?üX†å“/Lñ'˜Ã×@£VÄõ.xøÍö$È“S!"@¨áƯ71ĺ×_st¤ÉП¯ ÊöÑ­97vå§Qh`§,  þüP ’‘s»nÁ{-Àoä ©®oÊûøF‡D†Oè¬G<0¥¿6 Š@¯®«¨$ªÆé“àd¶Bþ%ÝF‡þH;:±X¨/MQƃÏÔj.Dð[Ù'¤Aa8‘®ni_G Ý€l¦ðÖÕ'Ìø´ÁÆÈŽÝQÕ‡ðÅKUóz>ÏŸl¸0þôBÖ:u|FΜÛ ˜¦è]Ìá«tòUÙÙÞ.ðµ5ƒ45é%FVѲ¹¢je ›*ÁÎto,à‹ñëÑl…þ{@‚G³¤ð¹SÁ·…RÐùþ[Æ, ÜØÿGmÌá÷"Ûe°üMçDUÜz)ë\sׇ֚¤;úׯ–š¨hƒ½­b¸’§ÒŸ”"ûë/ÊÇ{`ž†¬  ŸME`Ÿe“ hu" 7= ¾–  öt9úÉÞJˆãF¹‘áék„ßc4HRáX#:ÿ+w²ùïy2ëlòú†]ް;¯¿vÓÀfWÀÇÖ"Åþª$+?ÛY?2qèÞ{í•ô_>QèGò—tobïRà½àôbn´ö†“xY§½HŸ%Y¢KÅÊÎbêL `”šGG)q2ªÄd˜ ³Q$!k–¨ç ÆøÀØP :³Øs ÝwçUç2œ.÷YÕøÙ7?·Ô—㻓&ôílfº~OË<Ùßx ¤ÙiÏßK€5$<âPZ:W,e@=„Q.sãf§nœ= ydÈ)#`àŽ» ‚·ÙëИÁâÅb<Þº¨1awŠxçÑŒ ûMïd°B€ŽfÃòxò•кÚ_öÒÕñÙÊ/‰sC³Ì™Q•~.¸)˜#g0ën²÷ã~ ×&áÓ¤™ø›‡ùæðµ4ƒÌž»pË7 d,¾0ŽëC“h½ÿô‹b§ò¾>oÅ·„ãU¿CÈE3Ê"&î­j"®Ì)‚±¹ |ÌQWûzÅÉßEیތƒtæ$(= ×5`ÂeªñQ9Y`V.wCÚéŒ^Ìza¸8Áf3 ›‚“Ö jMhlé'€Ä8x„n~˜ó¡Tyt$~kÍø»22ÌhÍó  BhÏ>†¾×ž,ˆ Ô´ÐÒè°æxí‚›[Ë çå/Z,ÉÑT˜¥®fŸ[¾s§œ!H (Óå4„š¿W2¾ìÁ&ÌÛ£¸à¹Å®FÒN‡¬a­É…ÓàkmNù\èT2£Fe5·ˆñrk¼~#Òpƒ(øçQªäÊ Ðkƒ^°#¾¼Â;öŠÈE*ÒN}x Fú©…Èe’¢8Ôô*²¨^ãIˆ’¤QR³¢Œñ1Q ™ÿˆBœ4Ýô .â÷CxÛ0¶Ô:ú«`"¯ÓïØxr”‘¡„¿¼n'ö`µRŽŸìÄ”k¶E5å*µ6bD¶ï¿zN¡·œ£»¨Ø‘¼MÅ ¶#fY€(.ÎJ5 h"2|ì\a‰àü|âÁR˜ÅÂoM=½ê¬³ªŠ¨A42\Ñ¿)"îhnÔ®ÀƒÝ€g§  Q˜ÄÇËP_:ÐÌ<°J7ƒÅ£(¼öõphaî¯WÕ‚ÚÛ{Ô#‰© Ð+°xQ¼PÙO”Áç2hW.ƒÒÙÒ` ð,û×F!Pˆ|b¤¾VP/æ3P×€QU/:º—bµIvDê¶¿ñÌŠ¼ó"ŽHRBÞ OM€Öx¤î‹€Œ°»ùã':³Úh|”É!{[!rÓ…ÕŽÝ؆¨­§Kȱy€^¯šÏÇ¢ñ± ÈJO;Èì÷£­XÏFãWÀ÷üa0n<Ʀژ7/’2JÊÁÏ[ça¥‰èU·¸³†Uéµ›!jûÛ'†âóø÷ܨ/׬¨‰~pwC©/5®+áï_bÅ Û­ õøÎ`G¿ua‘Óžú¿ ž|ðØ@À|-¿žõÜ €g&c7ª9y`gd$ÊÃЫŽD(—»@í¼(ŽýÅŸ¯r7øŽ=ÚÑ_è–”Æè‡vêù˜-­ÐÂË+HÌÆò,ô*µÒõì1Ö^Ñ4üZ³Gœù0§žÿÓ?¹ó£)³Ô˜/Æïii¨/¦´6¥eåìÊâ¿6ÛãЫÔ1Ä2à¹iðý%X%•`—lê0§Œ=Sidäî‹Kwø–Œ( ”®‹`oØS½2ÿ·1>NãÍxzÕvËϘóôrWõËïª&"3'~]ÔÜ v¼ÊŒ»Nƒré,(çÉùmaµéÊÄvñÈ; Â@1à«*Ø’ È´aÅžh×§‹G´Ó3ÀÎÎ+¿ÐÝ j@y–mÏ¢qˆ8€ý”áв–&'„ Ϧ‰þ¼kŽrŒUö’°\jŲJØé™§DLvUó;›\h¬,ÐÄt²ž*—‘ ˆ’N}S•èVv@Œ£êé³pyW¶HG‡Ã ‹41WTżó§ªAbì=M-mÄãÓ1Ö;s-ª± :;’äfÙiÒ|ÐױȼS$RY ÆæJäÌ¥óÌ+ zÂ6‚‘• fùfæèÎ8ê1eŒ™õ·S¬­‰bšš» $$ÉÂ9¤„}‰BÚœ “þÓÓEëžÃõ*h‚ÒÓ/Û%Þ*(vÚ¬6 ¯µ ¤Þ|NH„ a×ÉgÉ.±•R“ýØÂ?Xp¯{ÅIÉwò¸&EhçÑåœÔÚäy >»„óq_ÓPšŽ¡”:¥ìŒ¬%m΂ 1Øá$-ÉJÛÔkÁZ:Ù1in}Kµ˜ú$¬Ò2Ú‹î7‚•“ jã €G6-N@K±kpÎ=/JC‰Î| ߲ς_¿€æ_f¦ 0y]ðJ¶?1Þinª%œ¾Gu¯¸– e~Õü±íHš§Ø6¬DÞN^w@d7Γ£Už_–ëÊ+„Ð÷€ïèc ÷¶¸§Ä“éKäÄX8…æ¦Å†«Ôƒ;‡HЇ„<ÊÕâ#Ê6nõ»|$ØáKÑo< ¢Ò`çÊXjIe'Fcú HSc¯*¶"Ì9\‰ôÊš¯åà<Ù,,†¹›ß¡×Ýæè§ ˜_£‘[ß4«pN æ –Wƒ]\ÛÉÅ¥’»Z_Åÿ)±æ©‡G9d~Œçä¾"ãš"*DQ:#ôÝ·vâ ‘_>ªQ‘=¯w2~±òg^sòêx…á-¾ó-˜½ÐùÔí»W¯åÔVy•Ó'ØüâòD>¸ê~Ã> Y¹±³”ÉÑùJÀßÙ Dg_ehÇ_‰xÅI¯2§\²Š§“&;kóÔ°(<çt<_­O–d þt l‰1Âq鈱iÛö‚Üw ÐôhR7MÏc'£c›kcßüŒçK}/‡ì^M­yÖÿùò©¦® @s‹–͸gÃNäØI±Êüá ƒÒÓÒe´ÈŒã]í¥ãÕrŠh£:5;˜•­\R›ÕµÔÃØ¾´ã%'êÁ§Ãæ0°çXe›—døH ©ÿÒ•Kˆv¬k)0þ@ÒÒGæíôK®»ˆÂ"KSã ¶ž©«IXæÏLñÉÙ ð ÷É+³v˜«cšrí÷³ K˜Í´„Ö¨E­rZ&³Èõ`lÝ-6Î’îÛÈr_àé…;¾hÇïÖvÐdáyõZˆ–”Cdksþò#> ™[ ¥­‘yñÍ TãÑ[JFCðp/¨à 3‹Î¥–ÂOIZÛ Äšqe»¤RÐ;¿8.Ñq̸3ïºY´n›Z®µë›rð¡9YÙŒ^ìÕk4!8^Úɨ„ÒÛʹã€'®,Ù1Cf¤ÞðGæ |Û[…8KŒ“˜x4–€f僾s¿ÈLƺä÷G½xÚ5 ÃKcyøîU€öŠ“®Z«4зÔ *1M)ìAÉ=½ìž/tÿ à‘~ðýöIˆÜz—C¢}]hÖ)¸_:ô„ü—Y½ÌJFË}‰ÛYæ¾Ä`/ȽmQmH^ÛXƒå()]5€¶˜C7ÞfiEr#œ^LŒ‚ïÌ1æät0 'X¥žgÓ˜óä{ñiï¿+:];~Mã#Bb`iOW{cè»n;+qÆP$Úé磎‘æbÞ4€ÝáFÐZ¯¶cΊQRám;™ƒ´!ñN>9=jg3È—.8<9š#ž…dç3^œç4·r5£¹)À£CÌ)œ¼¾Uç îmeÖèŒÃº %Šf€3ÏÅáà@.,cé N¤%ÆDÉ¢'ÆÜ4hlS ]èo2s –nrʃ¹¸B{o÷ 6™b¨ÚzŽùü¼ QtÀ¡éÙ`켬Ò-bBªàñÔá•ÜQò> øJ×õßÏfÜüüq þ 0·Ô-h5ùiõìQgƒÅ½È`ô¢~/[¶&F:÷U¼Ù•ùrOKÌ ¼”ò¶3_rhâª4—˜*,‚ÐM·$Ì|º-מÐ x¨'&"š™ ‘[â5`áÀ¶‹Ê tàí¢Tîºp]kÏÕAµã¿ÖÎÜ\çOÂοå×6> ê…A¸¥CÈÃp¢Ãh¸É¡ >Éx¸Úô"Èíg—$tÃ#|Þ‚u h;3KŒ>R¯ ¤<1¢^<%ø¬ÃMc lÌšš5ìw)ÞáV›×bp}f<|ùºŸŒ¨ŒNÈ]Íì;:sºECí {Íä°ãFfÕÇhE-˜UÛÄfJ$½xùZùìî3σÜvvéÂx´ƒ×$ %h͚ǔÃõÛE[T"- ÷Ê…33·´˜.MË£nWTN)9G^÷&ð?õ]æëîZCpÑØøè+›‹d價sÿ’TC£v”ggþí“Bv7>*Š•|IÅNêš=(>kšO[JıÉuÚ¸6œÒz¤+=qéÂYŒÈÑ[@» Xpmn‰ã ½ýy®Šmø-ÉËŽŠûÖ×ÉhÆÉO™˜Hîºn ¼¶m1ª©kHÈÑ)0‹|Ôá¢qÇ“‘ÈHÆúâNmíN‘:WÏü†q÷8åfU¿±±cô̤_qZÆO2Ñd „c” aß&‘EløZK}s0ëlÇã^MWw·‚¸2šO\ç?Öcµ†\ÈpÛƫә£w"¦hÊË‘ - HQ…¨»°6”/y.Šë}cü]é¾ÈîÛi‡–%¼c†'Y f’'¬“R€æCl N5–hy¸S%÷v:À¼îœ‘x†4v…YÈ­±µ'‰ST“½Î.ØZ£>—[ØF›r·|<ž\´ Œ†½ŽÃ—¤®tA/z;DO %M®×Ù7\Í^€fŠ;‚$=°4jɬ‹vþ„P¶KOÎͺÔĬíM¢+ziN\.„o¾pýnÁOEÁo,eΡgDŽE9¿ÌÍl•&vZÖ«H¾åÐ2>£…;}d9Z¦œ’Ò5h2ß½¤šÜé ð‚q室>Þ•¡´œý†[ÙÛ%n\•“­2kDHLüp®:/BCxæ‘ùt)'JzÁ³Jûy6)ìeÔ×ã´cmkÛñý¼‚˜Šv®µÌ¾ç~ ÷Ä“Ý%ÍÞ_é¼ÖÆJ‘ZŽ7Z!Ùd,ã­æ ­í-CÓc°"Â6ÜèÐïñêCsEO>x&fkÄ£<£6Ò¿l-ÿ¼NC=w"7¿yiÔc¥l†Éxò@/(¢Š°3¦ù.«i¥†…æ"†Ù±¿‹ÔÕ´¼¢,lãÈ}mà;©@dß"z‘Ò‹Ó‹É1Á“ñå6g–!¥){¹k"lgû˜”›µãž¹Æ¬3„ç–ÿ‚yëQo ¨9ù ï¸9y͸q™ûJwãýWÃpR~­…Ô7 bNÝòÚe<Ò +&nÈ;Z𨆒Ðën¿¦"Ä DåR3û¹x|0©a8r\Eb‹íòiMý]1MH|ÔcÔ“OèQåO‡wb>“9ǃ 6g<ùRb&».ëV\ €æÍÙ1vS*êŽSbî˜þVd'už’æße™‡æð„o,PÚÏ|¹Px&¥yòš¶Ðt>ÊÓä\0Ñ-Rq1:Èç˜uìƒÈëî]"Ë¥ÖÄ3Zó)ÛÎD_rê:Å–™–j¼ …#ßóÿÖ¦:¡Ç!Š™¤äÈ+p ‹j¸Ö3Nšåw—° cøÓõh105 È&¶f¿STÆ™[ê¥ÊK\ƒª]éJóK qÅO^CV­Ç‘Ü”ÔÔ½>~‚ÌŒƒÒxdÆkíâ îzG‡kß]ù°-¡óz¼[Ú4„œÜÝÆ6Ê%ÀãC .¼JoáŠñãSׇ%ïä€XéˆBôÔ`ä®&欵;BŽ™¹¢åÊâ|<)suîáUG’ÌO¡ÕÙ†ˆ+{²ÍÁå¶ÄPy²~Bp«ÖBóŠ/³,½ Œ„„ÀJFf;sù8·–b,²½0EbN,æŽìô˜°BB&W¼äÌ3œïÔàC/Ey¥˜HKÖ"×ÂH e|tö£–õb àúÉ*³dËjtEwu X¥›E1U4A¸•»[bhóš§|8Û¼µf,4ž›Q .Àí²s ر½äžæå Uqíç ÐwïȡЫc¬¨íô1PZ^Z[ŽÚ2Æ>¤µ xÎEÃy(‰Er–Lßy Hcƒ±‰±Ä|‡T1ªÁ¬ÞææÑ×w]sÊk™ýQ+g&@è\Ÿ”a¥±”WaY€gc×o#ÌJóîç¤ôØ1'fæƒ¾ç „þ>µÛÙ}È’0PïyƒÐ_öÖ"¦xáû¾ú94büYìcSUl6^G]»‹Yé!úÚ›bàYB…Ho¸ÉQmŠ1ëg炽¡äŽI¼1#z phQcã› “7… ý”AÑNBáj…û¯g|Uëh?xnN´b½Öìôbš·VÖáÓü`m¬³áFGÓÏ•P‚®J´-£¯åí'­_Ü?óŠÍ‹“‰´XAŠKÌ'J "᭄߲ºd3™•”¦&Aíj­³p8ü;Ý€¾žsè­Ô ÖÆZ0·îf4£8š¶5ªbÚ‘&á¦#ò“â±K]§ý€ùêgž*€~å’PÃçŸÞ2¦Ÿ¹b {L 7k-Æ|ûE'Lw=Všr€vþzÇÇO›JÃ&uxoÈFwXeº}§!¼\k¿ò؈ˆ_a žö@”*@ÎfwÕvÆ“ëD¹O–šÑy: ¡ïS>2<0·˜SÐW¯U}î± jªwZðÐŒ;‰›Ä=wÜõкÚÀ×ÒÄöõ4Xæ7ã Vr©~0kvƒ±õgÈ“ËcäÑ ÃÅ€B¾HUó㟽k&íºÔô5üz;<µiT§ï™²ð;tµ¶›z*£!Òô$h­@â|½ç.û£šK+A¯ß 6/9péøa¨° ~D‘®âïÈÛ!ˆ¢¶F«ÐókÏ#/)S=SÛç(¼k¤÷ëåP·hˆi€4zÔ¦—@êï˜{+é‹×.ªcrQ™£å‚E¡É4Éþ±Jéw Ó&›ƒ÷Åü°V ¯®A*‘§öëð±Á·Ø„Ü­ƒÒÑ Ã0é %“'ÓÌ<0«w€Q»3* 3ò1çÃôD¦†¿R:œyäÔ£{—\$¾*}•_×îpѤå»kÖ¤ï³ÑM6Õ_‹RÎ΋bêÏòy…C Zó2hfÕ°*·‚Í«âÜ# Óý˜~7Ó'îþì¡x5žW/ ¯Y7}òɼI>|9 ï‹Ø¨ØÍZ ÇQƒïÄ“¢þl†ÄG/T¡çÞw»(p29S0,Tá|­åá;ÆÆÙ× ¯Zìê¿~â¦)KzÏ´ o×)QWZ¤ÑÅPöÓ€‡zyÕq±-ÅÇøq9˜5;…ˆûõË:_íð©ˆ^ñKð ‚ï_yøö—âµÈkÐÎ×A{Þÿ¨ªhóc&þĬ…n7)(®Öšñk¹»UÌÿFžŒ@Í.£~¯ˆ' »8gÃ6Û©Yœ_ÏNƒÚräŽFg媚߷LŸ?À8òv0ênc5\€L%Æ‘Ïebòµ<0~}þïß:‘ÌK\³€v¢!ÏÈý`o ø',ò@˜ MÔ¥>„gñÔ(ͧ@éjN­©³+¹xbdó60yb$+Ï5]Í\D’†Q[ŽL¿©ú¿uõÎõÁOïKº…XÓ€¾vÕÿÍÓÛÆ"ôSã&ÜmàiôÅSU¶ ã×êÙ£€ÇׯܯOÎ/c×-`—»N›OŒLæ(ôgé˜ücçÞܾœ—»nÍùõ­ÿp,Ð7~ý¤x³IÜë¯qxäžvPÚÎ8C+× á”ù¥`p‡¯¼R¨®.F/ƒƒŠétšDÿ=SFßÉ—C'N߯«Â¨è$¯Úÿ-#¤¦ß>mÂßÍØPK¨{}¶zá%PZN2²–»S¸àzÛ÷Q»ËµcDPkF/²e8•‡àS™¾ð È¡•ºúu èùo޶þÕ‘ªq*Ý?cÐ{"UêÒVfÛ ÷ƒÒÞÒåvæ8†Ö°Eb$SÔ'[Õ;ÀÊ+píã“5ü:Ÿ£ÒÍ‘¤CÁƒý+}CÖ/ ç×]:¬]Ì·†@ú³ØïŒÈ·4ºóê­ñàá˫䰬9…öu»Dgõµs°á4]¢#Y*ú瀆~¸£v´ã§÷Ý—\lÝúšˆˆoв\±à¿ÎÚøVBhÆâßÎ)SU.]µõ¬3ïpµÕ‡ð9Š9E`nÝfŸ£¨ºYÃt|GJÖãz€^`ÝüUñ¡~tÄÄœ†ðúÉÍq”¦ÆA}é7b,JÉø5nI{Kè;÷E¥%!0Ó%¸´Qµ¾™HÿöϤòsó½Èâõ×c½{¦,ôî0A šïZÂøµÜß —Ëé0õÔxоØåU¢¬SÌQtIŒH1 Óž …þ¨P†Ÿ£- ¦|i¢è(.µêCíjZf÷ }6þÛI ^ï ™¯áõ×êù€f'Vîtæ}|y¥ ï¾ ìâò(2|> á|•ÊS¤ 4ÑŸj<Ùt‚€}Ãß=—?4mÜ;EáÝ7Ø”ºiñÔ¨mç„)ïo\¶úk>4+_Œ67oêSnŸ‚`*K&/”úè7²aò¹gƒ÷­º c†ÈW R6m*÷O˜è?Gl(wí–±,@Ó"Ì'_nHò”+šž fÝn0¹Â*ï¬vz—XÙ9›§‘¯°ñøÛàÔxpÐ Ð ŽˆTýõ“»&Mô±IÝešíƯyX×_kgŽž¸’ØþF1‚. ìÒJ‘å D.@fŸ•&Ñ¡"¾] …=þ™·­ú‰ ã»}hËCOfêìŸ#ðþ9¿É$àwãׯԇœ<6? á…öÅ›Àà DE¥®…öæ È“9 ùY¾ßÊå³Áƒk¢¬Ðt‚Ö낇3‡Lí÷ÆMúùmr ó#Å«6¥ù$ûýÊx¡}fsø^/#n•pûŒ•œ.SÑ_WSéùï­ {€Nº÷Ð!éTcö–YŠÿÓŒ‰þH·i%YH&ø" ‰l£t¹Px&* "Æ‘žpnQ15²$8_ À²´ðÏî´N ®Vžìz™_÷M×ÏZð¡I“¾Ó¤à A¦ ÒÈhgêY¸L•ñd³f—¨„#Ù®<™ý-ÍRéD±B¾Q ÐïÕNõ¦JÝ…èU¶nþò þáñÐ#¼?DÐ~‹ŠÂ§EžˆF]žFW:úëùú‘á+Ú(ê.¢)´çíªDsx¬Î*È:sê{×|—‚èä/Tóé§J&½#Bðç,º“¸Õ‡84Jûyæ8žÊ,±±u/X%@µÅ[#ù]Õ0ËUà‰L™>’…Íã¿ Þ½nÔ*=@/#°«‚‡Kg õƒ“&¼Ó ¨„DQ‚§'¦¥UÔ(>jdH´­ÜG¿QàC?zòošZo7Ùô2/^¦Ú©›{f(¾?LÐ=º ¹4Îiä2¢$Sþ~X"Áÿ¾no Æ pèÚt€MåQùÈ®þü•)ís­Y`¥KÔ¨ðÁãŠñ‘×}æ®® BëZÛÌô ¯úà3Å#†}Ϭ‰î×}Ns½wœ'K0•#Ó¥*|¯\…#?ÿTâä´<@{€Ž—†È&) Ûèþ›þƒ@9,@Cdv¾íýð%›à_¼ Œ¯Wzáz¬âO>Y?mã¿Ð }£!ùt~$û©QkÐ?1ôë[ò¿øÔgîêDyše SŸ è:ÝfÚô/ BßîÃ(\ ÐC¹Ôü¶uyäRãïŸóî’èU·øüÆ¡Yº³ÜoD”å7?º#«ÐÞòVª-ìÝoy€ö–·<@{Ë[É_ÿ_€RwóÜ2µQ¬IEND®B`‚././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/_static/favicon-16x16.png0000644000175100001660000000125415003227510020566 0ustar00runnerdocker‰PNG  IHDRóÿatEXtSoftwareAdobe ImageReadyqÉe<NIDATxÚ„S]HSa~Î÷ý©¬e.„#¤lm-5[ˆRlD^TtÓm^vZ]4¨+³ pwA7^v$ôG¥¨ÁJ”ʵÑR”à2îïxÎÎwNgg‘-çzáû÷{ž÷ýÞ÷} i62z~ä$í}Öµ‘¿€åŒãJ¦ÞQ¯–Z°¼™<¢Éã&ŽMä›w• B#.²"ö™ãÑnK|šrŒRŠÕÝ&7¹-Vû•ìÃó¥ÝãâTÏ™>ÇCÖ·“5DÌ•MYµÚ zšÓ¬±ñº¢V…µ¿ÈžÍjÕã¡~qaý?yD·rÃNQ„íõËj|Š]Í=¾MwŸ%Ejf¢eÀËíkAÞå‚9ò$ñ™Î øäRcçË”Ú:ˆÞf0{5L³1ØÞ/|ôÇdwTZ¨™ŠuªÙŒ\‹òöbf– íI%!:¦“|‡¶º ­ˆ/%Èt 1ö{·¡Yô‚OC˜«o0îü×°>U•þ`ÖôV)®zØïÞI'} ÑòM­àriØžÜM|û»¼ÿè=§+IÈ{ýàçfôó ÈòO#›JVì¸ÚJ³J?9#¼Ÿ z¯2üžV¦¸Û™Ã~ýÄÄ4ìÓ’ a~êE't11_û(çÜÒSNL¨,çÑS‚.éJrþ%ÀŒm ,æU¾IEND®B`‚././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/_static/favicon-32x32.png0000644000175100001660000000274715003227510020572 0ustar00runnerdocker‰PNG  IHDR szzôtEXtSoftwareAdobe ImageReadyqÉe<‰IDATxÚ´W[SG=Ý3³³3»°‚¼• ÁÆ ‘T’ª<ÅŠùySMY©â!ш Uù ±R‰ñ%©T™ŠDÀ QTV xA‚@Q$Â^˜{zg墠îÅtU×NÍN÷wú|§Ï× Ë²îñ4ï®ãÒ–º>ž±Sqi\‘aiï©îÑ´îV¿qMÜÝøâldjõ„¨HµM¥ª‰+¨:ÛañœÎÝ€ãÊYð·#ÿ/ÎBh]ñ Çó‰ ºb°þä Äö&ðƒ½Óÿ)ª ¾eåH¾öÃqVÜ´E™LÝ,ϲ‹å9Gÿ¸ø–q®ÎÙ)ÂÍ«6K`H"x02´Ø0<)lo3“ÉÊyVx¹ËÁ÷eC:óË–äšyVý3×bHAØfƒÌë•UkÙøÈÜÃûà ÀHË„±(zÞ¨ÀáûsfœœecuDhcÿ(cbáóñ_@õæ °ñm¶9’» –çŽfý=vp±½JÙv¨kJ`N­–rP×–B-ªdE›ñRS,S‰^„zzü›«#«fEÇqí‚݉¡A]W ¥d £¾NF=ß{Üè0ôœ•PJ«`z"+%JâùßÁE„iÆ X´ÁNÿ…Ôx tblFù¡‰ȼÕzRÁØLÞ§ü›L€/Ÿ™ Îòÿ¹Båç·Ož1°È~S>;¸­î[>˜)PóKa.d¿¬O5î~?œm§@G_T¸,è¯ÀqÓ‚›½‚Ù…Æqå4ø»7¡-ÏgÖ›  €ÿ»ûˆÆ|æ@”YgØÓµUELñƒ3Ûqq.&ËXž¤C>qÎÖ Ù 5 öø*©€¶¢ñ€„‘ž#+w*¡Œï8”ò¯vB±»ú‚dFqŒÌ%vŸÎ3óñ"+0#CqÅæeÀr@µ@`S•Kº|‘U¯ ¶Å~ßÚ²|XŒœ€Ð×e¿‹õ€bSñv¸t-ƒŽÏ{*–j[¼ºiÖu}‡ø—:|çžZhb-¿ jA¹EEÇOgî›<¼}à¥G2‰ÉTŠÓ?^&¶·@¸ÓeoŸë$´¼µ¬"n’ü±jš¼z¶mºrwŽKñL ÿA ÿA ÿA ÿA ÿA ÿA ÿL ÿˆIñ¹qx¶m»qŽk(ÿ^" ÿZÿ­eÿƒEÿH ÿA ÿA ÿA ÿA ÿA ÿW" ÿ¸n”ºq“Wÿ«cÿ‹Jÿh/ ÿ \ÿŒLÿA ÿA ÿA ÿA ÿA ÿA ÿ²k›ºq“Zÿ¼rÿR ÿA ÿA ÿ{>ÿ¨bÿ›Xÿ^' ÿA ÿA ÿA ÿ²k›ºq“Wÿ¢[ÿœXÿR ÿ\% ÿ³jÿs8ÿ‚Dÿ¸oÿ’PÿL ÿB ÿ³k›ºq“Wÿ^ÿ—Qÿ¹oÿ„Eÿr6ÿ°hÿa* ÿP ÿ™Vÿ¡]ÿIÿ³k›ºq“WÿWÿWÿm*ÿ Yÿ­dÿƒBÿB ÿA ÿM ÿ½sÿYÿ³m›ºq“WÿWÿWÿWÿWÿWÿJÿ¢]ÿh/ ÿ†Gÿ°gÿXÿµm›½sŽl)ÿWÿWÿWÿWÿWÿ\ÿ‹Fÿ¯gÿ¦^ÿk(ÿj(ÿ¸o—¿`½rw”NñaÿWÿWÿWÿWÿWÿWÿaÿ”MòºszÆq ¼z³k›‡B üZÿWÿWÿZÿˆB ü´lš¿u ¾r/«d½x4 ÿz6 ÿ«dº¼t.¼sPºrJ( @ Æq ¿€ €€»u^¾sâ»rá½uYÿÿºu ;½sɯgÿm3ÿr6ÿ±iÿ¼týv6½s½u¦·nþ~@ÿF ÿA ÿA ÿH ÿ‚Cÿ¸pý½uŸÄvªj ¼t‚¼rõNÿN ÿA ÿA ÿA ÿA ÿA ÿA ÿP ÿ’Pÿ¼ró¾t}³€ €€½v]½tã¡\ÿ\% ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿ]& ÿ¡\ÿ¼sâ¼v[€€½r :½tȯhÿn3ÿB ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿB ÿm2ÿ®gÿ½tȽw:¿v ½t¥¹pýŠEÿMÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿF ÿ}@ÿ·mþ½t§»wÁr¼sô›Tÿc!ÿVÿB ÿh/ ÿ”Rÿ£]ÿ–Sÿj1 ÿB ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿN ÿŽMÿ¼rõ¿s (¹s(ºqÿXÿWÿRÿOÿ½sÿ½sÿ½sÿ½sÿ½sÿ¤^ÿ]& ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿ±iÿ½v6¹s(ºqÿXÿWÿ‚? ÿ½sÿ½sÿžYÿCÿœXÿ½sÿ½sÿ¼rÿ`( ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿ±iÿ½v 6¹s(ºqÿXÿWÿ±hÿ½sÿNÿA ÿA ÿA ÿb) ÿ¥`ÿºpÿ\% ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿ±iÿ¹t 7¹s(ºqÿXÿ^ÿ½sÿ½sÿ^' ÿA ÿA ÿA ÿA ÿB ÿG ÿ`( ÿŒLÿšWÿNÿe- ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿ±iÿ¹t 7¹s(ºqÿXÿ[ÿ»qÿ½sÿh/ ÿA ÿA ÿA ÿA ÿA ÿ‡Gÿ½sÿ½sÿ½sÿ½sÿ½sÿZÿY# ÿA ÿA ÿA ÿA ÿA ÿA ÿ±iÿ¹t7¹s(ºqÿXÿWÿ£\ÿ½sÿªcÿR ÿA ÿA ÿA ÿp6ÿ½sÿ½sÿ¦`ÿˆHÿ¡\ÿ½sÿ½sÿ»rÿ‹KÿK ÿA ÿA ÿA ÿA ÿ²iÿ¹t 7¹s(ºqÿXÿWÿn+ÿ»qÿ½sÿ¸oÿAÿF ÿA ÿ}?ÿ½sÿ”QÿD ÿ\% ÿD ÿg. ÿªdÿ½sÿ½sÿ´lÿl2ÿA ÿA ÿGÿ³jÿ¹t 7¹s(ºqÿXÿWÿWÿu2 ÿ³jÿ½sÿ½sÿ®fÿl2ÿC ÿ]& ÿD ÿ‰Iÿ½sÿ‡HÿA ÿD ÿz=ÿ¶nÿ½sÿ¼rÿe, ÿA ÿNÿ³jÿ¹t 7¹s(ºqÿXÿWÿWÿWÿ]ÿJÿ»qÿ½sÿ½sÿ¤^ÿ†Gÿ [ÿ½sÿ½sÿ|?ÿA ÿA ÿA ÿN ÿ¤_ÿ½sÿ¨bÿB ÿUÿ³jÿ¹t 7¹s(ºqÿXÿWÿWÿWÿWÿWÿh%ÿŸXÿ½sÿ½sÿ½sÿ½sÿ½sÿ“QÿC ÿA ÿA ÿA ÿA ÿ^' ÿ½sÿ½sÿU ÿWÿ³jÿ¹t7¹s(ºqÿXÿWÿWÿWÿWÿWÿWÿWÿq. ÿ—Pÿ¢Zÿ™Rÿv2 ÿH ÿB ÿA ÿA ÿA ÿA ÿS ÿ½sÿ½sÿa"ÿWÿ³jÿ¹y7¹s(ºqÿXÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿe#ÿ·nÿ¨bÿe- ÿA ÿA ÿA ÿ„Eÿ½sÿ¸nÿZÿWÿ³jÿ¾y7¹s(ºqÿXÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿi'ÿ»qÿ½sÿ½sÿŸZÿAÿ˜Uÿ½sÿ½sÿŽHÿWÿWÿ³jÿ»v8¹s(ºqÿXÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿj(ÿ¤\ÿ½sÿ½sÿ½sÿ½sÿ½sÿ¢Zÿ[ÿWÿWÿ³jÿ»v8»w½tõžVÿe#ÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿv2 ÿœUÿ¨`ÿ Xÿ}9 ÿXÿWÿc!ÿ™Sÿ¼sø¹z,½s½s¦ºpþŽHÿ]ÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿ\ÿ‹Fÿ¹oþ½t­¿x$½r:½sȳjÿ~9 ÿXÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿXÿ}9 ÿ²iÿ¼t˺u ?€€¼t\¾sâ¨`ÿo, ÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿp- ÿ¨`ÿ½tã¾u^€€¹t ½t¼sôšSÿc!ÿWÿWÿWÿWÿWÿWÿe#ÿœUÿ½só¾t}¹t ¶v ½s¢¸pý‹Fÿ\ÿWÿWÿ]ÿŽIÿºpü¾uºv½v 6½tIJiÿ}9 ÿ= ÿ´kÿ¼t½¿u 0ÿ¼u W¾sß¼tÖºrNªq ¿€@(0` Ì™™f3€€¼s*½uƒ»v€¹s(ÿÿª€*¼w r½sÛ½sû¼sû¼sÙ¼wkÿªU¿€ »wG¾uË»qÿ«dÿw:ÿ~?ÿ®fÿ»rÿ½v»x@¹t ª€¼vA¼t¡½tî­fÿCÿU ÿB ÿB ÿX" ÿ†Fÿ°iÿ½të¼tœ½w:¿€@¹t ¾u”½tçºpþNÿV ÿD ÿA ÿA ÿA ÿA ÿE ÿX" ÿ“Qÿ»qþ½sä½u ‹¸€ÿÿ¿s ºu `½tä¸oÿ¤_ÿe, ÿB ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿC ÿi0 ÿ¥`ÿ¸oÿ¾tÞºu YÆ€ÿÿ³€ ¿u W¼t¸¼sù¤_ÿu9ÿK ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿL ÿw;ÿ¦`ÿ¼rø¾t´ºu U¿€ ÿÿ½z 2¾u·½tð´kÿ|>ÿN ÿC ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿC ÿN ÿ}?ÿµkþ½sï½uµ»r 1ÿ¿€¿v ¼u~½tî¶mÿšVÿY" ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿX! ÿ˜Uÿµmÿ½sí¼u~¿v¿€¹t ¼uo¾tϺpÿŸYÿl2 ÿE ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿE ÿi0 ÿ˜Uÿ¹pÿ½sнtp¼o ÿ€¼u L½tνsö²iÿ}8 ÿ[ÿD ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿJ ÿk1 ÿ¬eÿ½sö¾tϽt QªUÛm½sƒ¼sù´kÿ’Mÿ`ÿWÿNÿB ÿK ÿa) ÿ‚Dÿ’OÿKÿt8ÿT ÿF ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿI ÿ„Eÿ°hÿ½rú½tÌ€ Äv ½s§ºqÿs0 ÿYÿWÿVÿKÿg. ÿœYÿ¹pÿ½sÿ½sÿ½sÿ½sÿ±jÿˆIÿ[% ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿC ÿ]& ÿ²iÿ½tµ¸qÄv ½s§¹pÿYÿWÿWÿUÿp3 ÿ±iÿ½sÿ½sÿ½sÿ½sÿ½sÿ½sÿ½sÿ»rÿ±iÿ‚CÿI ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿªbÿ½tµ¸qÄv ½s§¹pÿYÿWÿWÿc"ÿªcÿ½sÿ½sÿºqÿ¯hÿ§aÿ©cÿµlÿ½sÿ½sÿ½sÿ¼rÿ“QÿH ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿªbÿ¼s¶¸qÄv ½s§¹pÿYÿWÿWÿŠFÿ½sÿ½sÿ¹pÿŠJÿV ÿD ÿJ ÿg. ÿ”Qÿ¶mÿ½sÿ½sÿ«dÿP ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿ©bÿ½s¶ÆqÄv ½s§¹pÿYÿWÿXÿ±hÿ½sÿ½sÿ \ÿJ ÿA ÿA ÿA ÿA ÿH ÿg. ÿ§aÿ»rÿ‹JÿG ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿ©bÿ½s¶ÆqÄv ½s§¹pÿYÿWÿb ÿ¼rÿ½sÿ½sÿv:ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿD ÿO ÿF ÿO ÿe, ÿ~@ÿƒDÿx<ÿ]& ÿJ ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿ©bÿ½s¶ÆqÄv ½s§¹pÿYÿWÿe#ÿ½sÿ½sÿ½sÿp5ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿH ÿy<ÿ¨aÿ¼sÿ½sÿ½sÿ½sÿ·nÿšVÿk2 ÿH ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿ©bÿ½s¶ÆqÄv ½s§¹pÿYÿWÿ[ÿ¹oÿ½sÿ½sÿLÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿB ÿŽMÿ¸oÿ½sÿ½sÿ½sÿ½sÿ½sÿ½sÿ½sÿ¶mÿ—UÿW! ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿ©bÿ½s¶ÆqÄv ½s§¹pÿYÿWÿWÿVÿ½sÿ½sÿ³kÿe- ÿC ÿA ÿA ÿA ÿA ÿB ÿ|?ÿ½sÿ½sÿ½sÿ¹oÿ®gÿ«dÿ±iÿ»qÿ½sÿ½sÿ½sÿ¹pÿ€BÿP ÿB ÿA ÿA ÿA ÿA ÿA ÿA ÿªcÿ½s¶ÆqÄv ½s§¹pÿYÿWÿWÿs0 ÿ·mÿ½sÿ½sÿ­fÿAÿQ ÿA ÿA ÿA ÿH ÿ¨bÿ½sÿ½sÿ¯gÿt8ÿR ÿI ÿZ$ ÿ‚Dÿ®gÿ½sÿ½sÿ½sÿ¼rÿ¤^ÿu9ÿI ÿA ÿA ÿA ÿA ÿFÿ¬dÿ½s¶ÆqÄv ½s§¹pÿYÿWÿWÿ[ÿ“Mÿ¼rÿ½sÿ½sÿºqÿ¨aÿn3ÿD ÿA ÿC ÿ•Sÿ½sÿ¶mÿg- ÿB ÿX" ÿp4ÿJ ÿD ÿT ÿŽMÿºpÿ½sÿ½sÿ½sÿ¸oÿšVÿP ÿA ÿA ÿA ÿMÿ¬dÿ½s¶ÆqÄv ½s§¹pÿYÿWÿWÿWÿ`ÿšSÿ¸nÿ½sÿ½sÿ½sÿ»qÿ•SÿX" ÿF ÿF ÿo4ÿ_' ÿC ÿ]& ÿ¯gÿ½sÿ \ÿH ÿA ÿC ÿg. ÿ¤`ÿ¹pÿ½sÿ½sÿ¼rÿ’QÿI ÿA ÿC ÿSÿ¬dÿ½s¶ÆqÄv ½s§¹pÿYÿWÿWÿWÿWÿ[ÿ}9 ÿ¤]ÿ»qÿ½sÿ½sÿ½sÿ²jÿ‡Gÿ]& ÿI ÿN ÿl2ÿ§aÿ½sÿ½sÿ­fÿR ÿA ÿA ÿA ÿM ÿy<ÿ©cÿ¼sÿ½sÿ¹pÿp5ÿA ÿGÿUÿ¬dÿ½t¶ÆqÄv ½s§¹pÿYÿWÿWÿWÿWÿWÿXÿaÿ…@ ÿ·nÿ½sÿ½sÿ½sÿ»qÿ±jÿ«dÿ¬fÿ¶nÿ½sÿ½sÿ½sÿŽMÿD ÿA ÿA ÿA ÿA ÿC ÿ]& ÿ¯gÿ½sÿ½sÿ¨bÿB ÿLÿWÿ¬eÿ½t¶Æ€Äv ½s§¹pÿYÿWÿWÿWÿWÿWÿWÿWÿWÿe#ÿšSÿ¶lÿ½sÿ½sÿ½sÿ½sÿ½sÿ½sÿ½sÿºqÿžZÿH ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿ}?ÿ½sÿ½sÿ¼rÿT ÿQÿWÿ¬eÿ½t¶Æ€Äv ½s§¹pÿYÿWÿWÿWÿWÿWÿWÿWÿWÿWÿ[ÿv2 ÿœUÿ·mÿ½sÿ½sÿ½sÿ½sÿ°hÿ‡EÿN ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿ^' ÿ½sÿ½sÿ½sÿf+ ÿUÿWÿ­eÿ½t¶Æ€Äv ½s§¹pÿYÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿ]ÿl)ÿ†@ ÿJÿGÿz7 ÿd#ÿQÿL ÿD ÿA ÿA ÿA ÿA ÿA ÿA ÿA ÿd+ ÿ½sÿ½sÿ½sÿf( ÿWÿWÿ­eÿ¼t·¼y Äv ½s§¹pÿYÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿYÿ†Cÿºpÿ«dÿk1 ÿJ ÿA ÿA ÿA ÿA ÿF ÿ’Pÿ½sÿ½sÿºpÿ_ÿWÿWÿ­eÿ¼t·¼y Äv ½s§¹pÿYÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿ`ÿ¥^ÿ½sÿ½sÿ¸oÿ˜Uÿk1 ÿK ÿC ÿQ ÿ€Bÿ·nÿ½sÿ½sÿšSÿWÿWÿWÿ­eÿ¼t·¼y Äv ½s§¹pÿYÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿ[ÿ’Lÿ¼rÿ½sÿ½sÿ½sÿ¶mÿ©cÿ¤_ÿ­fÿ¹pÿ½sÿ½sÿ³jÿk(ÿWÿWÿWÿ­eÿ¼t·¼y Äv ½s§¹pÿYÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿ\ÿ†B ÿ±hÿ»qÿ½sÿ½sÿ½sÿ½sÿ½sÿ½sÿ½sÿ·nÿ†@ ÿYÿWÿWÿWÿ­eÿ¼t¸¼y Äv ½s§»qÿv2 ÿZÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿi&ÿŽHÿ±hÿ½sÿ½sÿ½sÿ½sÿ»qÿ¨`ÿ~: ÿ[ÿWÿWÿYÿq. ÿ´kÿ¼t¸¼y ¿€½rˆ½súµlÿ—Pÿb ÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿZÿe#ÿ€; ÿ•Oÿ›Tÿ“Lÿw3 ÿb ÿXÿWÿWÿ`ÿ’Lÿ³jÿ½sû¼u˜¿€ ªU½tQ¾tϽsöµlÿ= ÿ`ÿXÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿXÿ_ÿ~: ÿ²iÿ½t÷½tÕ½v ]ª€*¹t »rp½sлqÿ£[ÿ|8 ÿ\ÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿ[ÿ{7 ÿ¡Zÿ»qÿ¼tÕ¾vu³{¿€¿v¼s~½sî¸nÿ¢Zÿm*ÿXÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿXÿm)ÿ¡Yÿ·nÿ½tð½w ƒÃw™fÿÁr1½t¶½sð¸mþ‹F ÿc!ÿYÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿYÿc!ÿŒFÿ¸mþ½sð¼t¸ºv4ÿÿªq »tV½t¶½sø«bÿ„? ÿ`ÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿWÿa ÿ…A ÿ¬cÿ½sø½uµ»w V³€ ÿÿ¼y¼v[½tà¹pÿªaÿw3 ÿXÿWÿWÿWÿWÿWÿWÿWÿWÿYÿ{7 ÿ¬cÿºpÿ¼tÞ¼w X¼y ÿÿ¼y »t½tå»qþ™Rÿi'ÿZÿWÿWÿWÿWÿZÿk)ÿVÿ»rý½tã¼tŠÃx¿€¼u=½u›½së±hÿHÿh&ÿXÿXÿl)ÿ‘Kÿ´kÿ½tç¼t•»v 8¿€¹t ¼vA½tļrÿ¯fÿ†A ÿ‹Fÿ±iÿ¼rþ¼t¸¹w :Æq ªUU¼w g½t×¾sú½sø¼tÏ»tZÿÿÿÿ¼y &¿v€¼w r»wÿÌ™3ªUU././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/_static/icon-ffmpeg.svg0000644000175100001660000000445615003227510020572 0ustar00runnerdocker ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/_static/icon-python.svg0000644000175100001660000000303215003227510020634 0ustar00runnerdocker ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/_static/icon.svg0000644000175100001660000004424115003227510017324 0ustar00runnerdocker image/svg+xml ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/_static/opengraph-image.png0000644000175100001660000003665615003227510021437 0ustar00runnerdocker‰PNG  IHDR°vxž‹ntEXtSoftwareAdobe ImageReadyqÉe<PLTEÿÿÿI‘ÅAŒÃm¶e­KнM’ !\w¤Ë¤ÂÜ3ƒ½öùû‹µÕE;‰Á ,m…²Ô*¾[›Êt½k¢Ìy½íò÷!~¾²Ìâo¸ u½­ÊáP•`§pºêñö w½Yz¼åíô”»Ù&eTš!aMìñöÆÙéx½Y A…Q”Ås½ AIŽ{­ÒÛæñðôø]¤WZ `¨ )i :|Þéò4†ÀDˆQu©Ð =€HýýþXžÃÔäèïõ|½˜¾ÛÑàíFйÑå\¢ 5váêò%€¾_ƒ®ÑÁÜj´ $aÙäî©ÆÞažËVœ 8y 1qÔâíóöúÍÞìJR˜u¹IŒ(~»%}»-„Àô÷ú Bp¸ 0qW 6x @‚•µÓÉÙéh°x½ <~½ÔæÖäïe ÌB„³ÉÞY’Á‚¨Ë (eT^¥t½{¼bªÍÜêt½P%}¼b¨ÀÖès½!|»Mq¼i²C .m\|½V a¤¾×f°q¶hœÇU˜É .o›ºÕåìód«h²j²r¸'hn¥Ï`¨[·Ðä 0ng®i° 2t»Óåòöù 2sC†h²r»h™Ä«ÈàO‘¸Ø +lÖåðÏàíÐßí ,j»Îá 4u¾Õè ,jl³^ t¹Š¶×{»`6ˆÂs· *hc¬j°_¨U[¢ 3s#c]£TSNWVa©Kr¼t¾øúü AûüýþÿÿXs¼þþþs½ýþþùûýùûüúûýVU˜Çi±\˜ÇVâìôãëóUñõù¥Æß@Y'G‚d¬/¹·×X [£ &c±Ñy¼ 8wñöù¯ÎižÈ°Ó Èã¿Òãr½ SÈÛêËÚè`–ÃÃØèi—ÁÈ×çŽ/à:DIDATxÚì}`Tåï%'U{x48Œd¶ œ•‰‰dÌV°R“j‚6¹´:¤dS¥- ›ÊZj–»Í–rQ4uK6·,mÙT²]{¥ ¡f/ FR_J»Ýz½n[jï]o‹{÷îåÅ^òòüΜ3sfÎçó·0sžÏó=¿ç÷»è"€tâèMÿv„OR—_zñ—>ÅçŽçÕÎZ?mÎÅr5Ÿ8š#OÞ·^?ÍÛÿÅe|à\î™0Mÿ€9ÿÝ">p&3îžýý,†çüßãSòêßÎÑÏgíW?ò >pG®¼o­>‡?óÛ_ò逓¸'ãa}¦]zÉk|@àf<4[ƒ™¿ç3GðÒäéú˜ =ð³ëùœ é½jÊZ}|Þþ…¤dîÉX¯+±ö«B!)$‘«ïž©+søÒ/ü„ ’ÃKïÌÕEÌÌø2Ÿ$£wÝ¡Kžû3 I á¼;ñaÝÃs>I!)$”k¦ÎÔM2<å’—ù Q\vÝ\=¦-¼Ž¤޾q…'3çÓ‘ÀÇ&®×ãgîåÄG öòuóáÕyüÙG_âãû¸?¾ðê\ÖO¹ŠBR°‰E—\¡[Ê´ŒÛøTÀ>¶p­n5³J!)XÎ?ÍŸ¦¬¡c³š•ÿÛé×ݡ VrÿÍ(+(»¦-Е«¾Ëúê’€e,ºó1eý4—.ë‡{ªnToãpßí|Æ` _„W‡ª6‡O³c{ýõBÒ t$ øcAxU^—7~ŸM»‹ýêéûC7ðQ@|Üólõð*û‡º:Ew[z”õöd I ]«^õWž««“ >Ûµ@ù/X{Ç]’€Yn¿O^íÜÁž¢oª§ïŸæC3Ü4A=¼ÚP´4< 9ÕÙ‚6Ÿàƒ)ßz\=¼ZQÖƒ»½ýêmnf¸ˆxùÚ9ÊŠùCî…áÕyt×îWOßo¹u_(sûõðª±vGx\7Wµ«7Q^øy¾PãÁ AåÕèáÕùéû=Qý–³§~iØ^ºWª]Õ¯xƒŽ¤nâSÕo97ç÷ †íf0ož_PHú1¾BׄW×=à„ðêü;Ñ>õö~3çÿ‚¯À ½óu]e.ÛN;&-’ÞËW ö|e¡“«ó£¬U‚á:×RH Þ\óSõðj(¿'œh6שGYë§<É Îá•úpT½´p0œx‚IÓ2äKH×ðê1u]½µsG89äì>¨þkΞ:ƒ/ ùþBud×- 'M;Õ‡®êoO~…ï ͸i¾zxÕïí '—¥õÙ’BÒ£|¿iĽ7 «ŻsÂÉf°G0©âð¿ÌW .,ºU^ëÚv9…‚IÓæ_Ã× áÕ}ê•WÙ«7‡Â¦êCWõ¹s' « «/{_àsaÑ-˜T¡ßr+w¢R›o=>S}{Õè€ðêü;ÑeêUYÃ÷ý5_8@ ‡W× Â+³³çí.$­Tÿ'<<áz¾t€åó‚ðÊß±9ìLv´ &U̼û¾w€äú ëÕ¯ ÖÃŽ¥»JeMŸüß=@ª…W—«Ï—ÐiË ;šÍeêw¢õ;¾Íp€” ¯~0]}—;2¼:?Ê ]]ÿÃOó¤ ONQ¯š‹ó©@ΤGQÖO®|'ã°ó: [QHºDeͽŽBRçsÃC‚ðjkmN8…X*˜T¡ßr+w¢œÍË“ÃQ=EÝáÔb°Ç§ IÿÀÁ\y…zxÕŸŸ7N9rv7 îDSH àXnËP¯¼JRd+¢¬® :’>D!)€¹únAxµjgN8eY*˜T¡Ïù(Ãu^ý@^%·²ô&U¬r%@ʆWC‰˜=owú^(˜TñðÄÛxBœ^M„W‹ S^W§£¬e‚¡«3§RH àðjê4AxÕµ#œ&t—¬  µxe²›Â«óÚû ¢,ýŠ;ÂÓTŽ|û õ%«{{ÃiÅàöõýá…ãH"ߨžµë»ÓLW§ÛûÕ †®N›ÿ ž€T¯¶­ÜNKº‹CW¸ü^ž€$ðê;¨/Tÿ¼Íá´%o¹úÐUý–¿¥ ááÕ·Nƒ¾@8 †®SH `>- ¯öMÊ §99m™‚(+ãAž g†Wž•›Â.`“dRÅ̩܉H /]'¯R¥²w¢Ë:Õ?—·'¿Â“ˆðJPy•»ÑÐUý †ëØÍ» áÕÖ¶œ°«È™$ººv"Ãuì䚯I«”ë€lE”µReÍ¿†g À¶ðj¶úbÊÏ »’¥‚ª¬¹s'ÀŽÞù˜ ¡)Ø>v)ƒ=5‚ê–[ñlXÍW»¤²ÊÚ}H2\çó<]–rÓ|Ax•]ßv9;ºE +šÈ˜‹²’;Ñï¼Äs 0ßÉô¼ÊçŽÙ(kR»¨ô(&@|áÕ‚BtG”U%¨Ê:¼ð]žN€8«¿9À=œ8£¬å‚IÓæÿ‚'àÃðêJIx寣‰ŒQV®à#€á:ïs[†àÚ Md,‹²¶2\@Ê I«FšÈXe †®®Ÿò$Ï*¸ž—0W «ò•›ðŒ…,-ë”’~‡ç\^Ý!ÐUó<šÈXM$Êš=•á:àêðJpmPÏ¥‰Œéûî&Áwð6ÃuÀ­\}·$¼Ú:‰{8ö°£Keéw0\ÜÈ+“%á•§ˆðÊ>$CWõÃ?üO/¸.¼ºB «?ÐDÆæsaOàë`¸¸-¼š( ¯h"“e|#so¦Ü^MÜrÖ·Ô^9/ʺåV†ë€+xU^eÓ9qQVà‚áð}ŸçY†ô¯¾- ¯ú½=x$‘Q–Oðå<<ázžgHo¾;Q0UßO™„GY³DÃu($«÷8v€ð* QVm¹d¸Î®é^]÷MdR!Ê*DYk§\I!)¤cxõÆ-]ÑD&™äy“*†'RH iÇ»?”„W»ÑUR£¬í ($÷ò QxU¾’ÈÉ&§v•¤ô: I!mxéºÙ’ðŠ&2Ž`S}¶¤”á:½ó1IxEçDY’¡«ë~ŒgRŸ_8,ÐUMde-b¸¸ˆk¾& ¯s•( aAºëUªËéÖò|QžeŸûÒâf„®Ö¶•4‘ÿØ%ꪮúV¶q¥eñÁžÜ±{e}æV¤¾°š‹ŸEGãñ¹@n³n͵–½êÜ}#‚4Vnàsøhü[0~Ý.²½Û-ûv<íAXÆÂʤ²B<4Ï£ÛI¹…­1–ÎkFX¦ÂòÐDf|6umÐíf±…/=ò| ÒQXC4‘QÉ…õ½Úºÿu naAú kaû¸jšõİÀž>“¤Ÿ°n¤´}ÜÚ¦l=aø‹rÂ2^­ü‘žPj–Ú+¬O°a¥k]û¤Yz¢i/DX£eXkôà:àëÔ¿kaÂb‡%csG¶žæí@X€°–€î•ßГ†¯ûu›„õç 8¦axÕ¨'“ÒÍ ÂR ¯z¼zr™Õƒ°aq$T©¼*ñèI§)€°a!¬ñØQÛ¤;· GÂqJjúug°µÇa}a;¬ô)e˜·Bw ‡z ,„5ê=œíº“8˜‡°a!¬‘Kv—ê#·aÂ"úÏõ”ùuÇ‘¿aÂBX¶Sw"õ9 GÂóNƒ¹º3é\FY ,„u6=ó²u§R¾aÂâHøá»ÁeºƒYаa±Ãz¿R´Ø£;š²A„‹Öi_í®ìw¶¯ôæZ„‹Ö)vÏÒÏ–Í ëjV°ÃJÙ¸½IOò-Ög ¬”ÍÛ RÁWzón„ËõÂìÐSƒ·º ,·gXæ–^‚°a¹|‡•S*¾Ò³{ ,wï°&驃oКo VŠ&X¶¸÷ïmªôv”ÔÕ×u”ù·[rí§7„åfal¨˜ò>Õì‹jšÖ¢"ÚlÈÚØñDüÅ©M; ,÷ ëu¯Å¶j_ÔŒ‘ˆU‡Š2ãýë» ,÷ «ÛÚ~}ÞŠ`ÌX0à‹ï~£aÂr­° ­¬ì,~3jŒG4kõ¶x~HÑ Â„åVaY¦«¡å!ÍPAk­?Gg¬< „õÕ?b%ÂJÁËgYÅÁ³jº:£¬8z΋_Xà RZX®­Ã´¨jtÿšjC‚–—kö•¡¬z”°ÃJvXR…µï™à€!¤¯«<t ¬4jÔ`A#¬ò®Þ˜!GË[lîçmØ¿°f°€#a î°žˆûÕ`Gƒ]d ’oîÚuÂvXḭ̂Ä髚,ͤ¯N «LeïOt#,@X®¤&.]=RØgÄALÛijôE„åJJâ ¯Vîj1âCk3c¬š„dXTºKð—µÆŒ¸Ñ&™8ú{°Ãâ.¡€Ü<Ͱ­Öog쎰€V:‘oJW‡6öÖÓLÜZ¼ a;,7²ÝLxUwxuÕò7„‹Ë9jáÕ›1ÃBb»‘÷l@X€°\yýYØr´¿&+jXLH¼7æ , Ãr#QQÆÆ=}†åÄ–ˆwyy –+…Õª~AæU½† TÿFj¬I 8º‘u-ª¯é²W7Ø¢«“dIo.GX€°\b ÷©Ürö…4Ã.´yÒ– 9 8ºòLh„ÆoM5´xMÔ°‘ˆ4wïAXÀË•gB#Ooí ¶«·çô(º•+5ùñÉÙš1ï ——Db†ÍD„7t: ,W¢FKÞè½’³‹ßÔ Û‰Õ »È¿ˆ°a¹1u_wÊ‘º‘…~oVtu’†!‘°<»^GX€°\ÈÏÏL8­ð^x*ó׉AÛ/Ûb5¬CX€°\z&<]¾™W¼õœmŽÇH”®N²G&¬¬¨yaý„•Âï ß’z+~—_ÙØ´eUû‚šŽÚPue»dÅ£Ï?FX€°\È ç¤ßÑê`ï®]Áj-f$”–\YÃãQ„éŽNá舵£Nà‘°¼1 a;…•;¬Áœ›ºŸÝœwšÍK»»7åä Ï„I¥A6ì«ÅxaÂJ=SmÚh«*ö–Ê\uìØ±òÓ;ÖÞ¾ï‘ý¾²¢»Kw΄I#*ºžãÑŒ>„+µdÕxº8w«§ÌîQ‡|uµ=›œ~&ÔDCû5£aÂJ6–x›·%ý{ VOZ:ÚñQGl±ªDg“’]gVX¿d%ÂJìÖjwñÂ)¤þLïΞû²üó€„%ëÖ4΄ Vòénóë73ŸkèXn×HÎJt È´Šþ)» #ö \(¬Ô*kÈÙ^¶µY7OyeWÞà÷ “NDôψ gB„+©l^Ùè×ã¤Kþîîså„3aŸèÑzêO¼Ž°a9XWuíº%d¬ìtØ{Â>Ñ)7tª:aÂr®®Š=ºeô*éqVíh´\òûç g‘…õg –Ý,-óëÖÒTõÁÁðõ–ä K{KòËWœºø¨!,@XŽ,º*ÉÖ­ç`Û s΄ڒ_ýøé?ósSÂú+–oŸÞ Û‚¿fû S΄Ú1Éo0΄ Vâ«D ›tÛÈî83÷=ù•Xšh¹æÌŸAX€°œEOe¿n'MËrq&ŒúåÂ2^@X€°Ò:k¿ð\˜ßã„3a´Ù„°5#¬{Y €°lÉÚ‹=¹ôwT''ÂjýšùöÃѤšƒ°aYRyUÙ¯'åÁd«WöK.;ëþX&¬a„ËŠðª,[wùAÇWaéYgýÑÑ߲ÄeÝEåºCÈOü©Pֽﬗ„cž Ùa²'¼j;¤;‡ü„'ï!Ù/X~Î/øsvX€°^ÕtêN¢,ÑÕ ^Ùïç=ç?аa%޼âlÝaÔ'ÖX — {Îí¤%Öe¬@X¦+¯ª¾¡;e‰¼¥+þv­çþùŸ#,@X ¯féN¤ó¸ƒ7XÍç½X‡°a%"¼Ú^ã×Ivkâ^ú„¿[倡t&DX€°, ¯<ºcÉLX9V…ôWÛxþßð„ewxµ¤]w2¹ Þƒ[¤¿YÃùÅ£ –½áÕî_÷ëΦ(!Á»V7,í)qA™Xa²uÚ w…îx*૊!q•X˯_@X€°ì»‡SR®§å ˆ±"ò¢Ž¼ ÿ–u –MìXöˆžxm±ª+Å¿”ÿBÆ4„˦R†Üf=UØ3`s€5Ï„E[F(=}aÂr[)Ã…ÕXöNÒÑž’ÿN#&kë ,ëKVfê)År;…ÚFw¾=#kQ„ËêR†Iûûõ#ËÆ+„“Ì”ù×xJý3„e%Ÿ x=zÊqȶÞXZ›©kI£\Z‡°aYȳuöveðï=”»|^}U×ΕUõue5³Ú­ÑcÀ._­4õîáà(gÔ(„e])C×6ÊÊ3«xw(ŒjïFû‚‘О²¦ø;meÚ“b‹‡Lý:…£ ¾®,¬—X €°Æ,e(̵¯+ƒ'÷@kuˈ«¸¥:¯c•S¬–Ð~s¿Ì±QkYש ëm„k,]õ”Ù^õ* ™2i‘³†âú¹-–û*zÀìò»Q™(„eÉ<ÛJúŸß9þí™` 2®RU«/è dUšÞMö޾i{aŠ¿¥è~Û ÛÛWÕ*D+â™rañèÞ:ó÷¾KÆØí­CX€°â½‡ãµmÀÄÞ’ˆúa-ZQjú­¶òLݸÅü?ÙëoFX€°â+e(9fÛ¥oCLh ³'ÓÍ §ÁÓê3ÈaЧ+CãMºj.Í[$ÖPl®Î>Ó²IÐ‘Ž¸*ý=‘1ÿöuŠÂz••ëÂӠ϶}‡ž3U´×ÌOûfÄ¢V2OŹ߬»wDaÂ2ÉæyìÒÕª%fßÛÅBf²–ܬ(ˆóßý£ñþÙ¯#,@X¦º2ì|®kΞy‘8šTE|&„Õ`A¥è‰åq ÏŒ÷CÖ!,@Xf Ûkì: ú}¡øFCËÄ?sCüGÂàÊø{B7Ž›ÚEÕ„õ +ÖY§ÁÕvû+â~c•Î…×Ûã­ÕÖm[ÇööeV¼¯k_|fB>+þñ ;Ëu –è4°m~—§®×šAÕ^YœT×”XR:«4Á§ïu„K¶W­²IW;VËæšeÕ›Uq•2X3ÞzHé~Pì¿«ë5V ¬ÓÛ«»î Î:ne[ª†­’Ÿ2^åXôï/S³õ:„K5½ªµk{õ£Õ†¥dIŽif#¬˜¥ ïñ Åß!аa©±´Î¦&}ÙóvY=0v@°¹1ùÃU–ukVÝäÅ~ްa©Ðã³)¼Ê ÙФ8ªþÛš«s>×dÝgP¥ß­SÖˬp½° m ¯ÖØÓS=¢Z+fªWCK(×ÂÏ r@YXQ„kü¸½­Ý]mè 6±F1_2¹DV[y:þ¦ À;çLˆ°a·weÛÑHfE‡èRL¬:òæ³Ç·oßÈ 5Ç뺫SK°äÝûªŸÞfém$ÑUÆuã k:Âw kG‘Õ ¹’žW-Á5eåÙþ3d{2½kÆÙ˜TTø%މwxZÅk¯#—eg ÖØ¾*ùƒ-á• œ ©zë‚¿áà8µ[ ão„Vd «UZ½Ëû€l‹ûG„kL_Ù «rQϫ買FVÅ{g•ÈZV©åŸD¼Êþ¬3á(ÂZÄJ· k°Èr_5ɶTŒy!ÈhSv£ÿáì6‘6ʬ/ô¯4s+èÅ× ¬‘}Õeu9ÃÞ*á{¹Ð8 ÙcæP'FëÜp£¨µiðw{­?ÿÆÔ Ê(;,@X#3ÉâæWþ|i™È¾qûhey0X4’jÊE«ÑB;š–š+šg‡5aK…(·v‰ˆ; G®À´7Œ“=o9/¼jZ" ýµ¬\;Êf}f;T¬CX€°FjÞÞdé ͬ/Ñ*qnß8Æi­-ûMyçé˜ÛÓä+ªÆDá•-=V—›î¨Ó‡°aÐ^´ÒÊê)‰È[ŠîRëkU5n9•VÝ{"Ê µ6£¢]^pÉ^;t¥¯6ßHþƒ3!„õaà>ÏÊ{8¾,3]çIôWö ­i²EWÍUñô¨X‡° }…5ˤ°Ú, nÍÍžïUýû·ôÚ¢«P®=ãb³Ÿ‰«§NߨÂú +Ü·Ãê±î(´­ÊdKQõ"°|mÀr_E:²mÑ•¾%Îa±1„ë¼Ë²²nÿòÖs 3Ú©þS6¶X¬«ê®r{t5T÷¤éu Ö9V½UÝSr+LÇËY’L?díi0`OxuÒßEѸ»¾±„õŸ¸NXÛ-j¢ÒÔGKQQêßdaŒÕʵkžY{…-¡[ÆÖQV¸LX;fYs§¤!ŽhIMÔs£Vùj—]á•Þ_fMKèuc ‹·„à6aYr löfŵ› µ‘o±úvÚ^éú#Y½èKX¯² ……e¢¬¡ÇŠÑâÂ8§£¶Jk æiV„W³ìÒUöJËö€-c ë2V¸j‡5hÁ «cKzcq®Ê,ñ-Ž×-­5vùʉY—²­CX/±ÀUÂj‹ÿUXÙ›ñïvŽË®·/®ú¦H±]ᕞYai¡XßèÂúo \%¬M߈»qJÀŠÃÏ3ý Ìÿ¼ê%¶…WÙKª KiAX@†u†x›Œ¶w-9üT˜ùáß4[6-´«òJòF «ya;¬S,ïX”]Ü`QÑy–©ËŒ+êÍThyvéJ$«År_>",@XẸVgnÀ²Wa'Lšs±¸šB³|ÚàY%ø¢† ´ ,@X'y6žY [—­Ë–ƒf'Âg¯½Ó"u+ìÒUór[úHœ9", Ê£ VvG«•‡Íü)-S]œZd¥Ç¶ÓàÁЀM¾:u&DXàúÖRó¬Ò€Å‡Ÿñ¨b’Rò¯íZ¶×6]mØ£¶Ñ2š°þ Â÷Ëô¥œUU½Vï&NÄWi¿,Ò2îîª6Ó6]™Kÿ%gB„nV·Ùã‘/ËúÝ„g¡Á[%­cìùb}yõ«lÓUÍ Ã^úFÖFXàaí4{MΖÝÄñxµ‘]º¬µz¤}V,˜UTÐo›®ô¦ Íf_<²Ã— kðÉ{'ö,ϨqxvSþ²Š7#»‚Õ}}}Ѿ¾`°!TÑåÍl¶ÏVºgYÔ°Ÿ¸\XÛÍ¥E v­É${Õó¥5^¯ÏW¹¿iC¶n+þâ^#ôq$— Ëkêí }ë3ºJO9~mGaûˆgB„î–©š†\;_†U¤š®ÚŸÓŒD@Xàja™ˆÜ‡Ûúò^ó¥”®®a” Ò_X›D=† “³Dµ¢Î$‡W;û Ç0Ê‹…é/¬¥¢—„þH’Ö¨öt2kÞËW'¡‰Œt‡5›…é/¬ÑÊÝ’´—dZá7“^ùB-†“y‡…°ÀÂÚ-Z»Þäm4Zò“^­Ñ gÁ \+¬e¢Õ»1™ëôÍÜdô¼:Pm8 vXàZaU9ºÌýÜ{…»æ%:z?æ¬ðЏ]X%¢IîJ¶µ'RW§…Wì°ÀíÂêå9Á$/Õ–P⎅þýŽ ¯ØaÛ…%*tÏNþ ÞU´"!ºrbxÅ \.¬A¯(ÒqÀ–C«Xœ_µ×EZ „à0aù$˸À—é‚õÙvßrö†œ«+„K± Ë V«(µµP4·B3 „âÂZí”%[½Ì¶y:‹Ÿª6 „àÄÐ]$¬:Ç¬ÙØ‰2[ÆSôªê5 „úÂZí U«ey­WÖ–º1a8UX¢·„ŽZ·Ñ5¹Í–êj[™£³vê°aåKVt™ÃVnuÀgÝ Ã½eYš‘°Ã× «L²¦k·vµÐ¼c^…RDWì°ÀÅÂZ-YÕ\Ó±Þ=5ñn³² Ú"1#e`‡®V½da¯Š:rk ËJÍ;Ë¿µ$5R‰‘…õ Ò_X+E;‘ S×°vbY©™&Êþ·:²ª a¤‚°v¦R{™±)\þMÑtòÒªP4Õl…°ÀÍš”: üÆg ZR¹AEZ JŽG4#%AXàZam ëwÎ_ÍZ0´§®2Ó3ZU©ßÓþë²®ŠH4f¤* \+,ÙÔœÒÔØ“´Dƒ‘PÅÆªoMéâÆC‡94kVcA®·£¨vM¨!ÕŒ”ak…%›KèÜÔ}dqµhÑêêê`°º:Õ4-f¤ \+¬²wk!@²„5(ëP’Ø—j-ÑÞ¡gó²B­»úÒe„°a™V¸Fwl騖UŸ»ïLzî÷´W® õ `+„®V‘îÐ3¡–ç;ïMŸ¿tc/ºBXàfaµÉ„5/A3-4b»«ýÏi a{…%«kÐ= Ùã´4tŒr9°y9›,„îV·ð ^mVd¤¨|Œ–­ a[…5xPØ8Êö Á§Æ~sÙBX \*¬p°ÁÁ{Wct͸ƒRÛ— k’PX[ì¬vµzJïƒ a;…Õ#>³Ò¶…±H‰R#¾¡2 a!,p¥°rš„ÂòØu"ë[¶Ju|àšÂBXàFa…çIÛtúl)wñÿ7v!,„®Önqcá¶ŠŠº…Vµ ,„nÖRq3tÕ¥P u+„ÍØ# a…5èo±ZZŒë=°Jü+µ ,„.V¸V>mÆÊ×tÑÂ&æÝxv!,„nÖRCýª¬2––åë45O°a!,p£°Âò3¡Þ¿Ñ’#YìÄj¿nŽ\ a!,p£°&™ðEóžøë]²Áü|ù ÂBXàFamÚkfhòÆx·8ÑÂF=N ,„n–øô™SḌ¥eÕôÇã+½a!,p¥°zÌ©£Î|É{Kk‡?.]éCÏ!,„®ÖºJsÒð5˜ ÑÒ»r¯/O!,„nÖëë&¥‘YaæXØ7éPܺ҇Âwî°~Ü—iRþ’]ÒMV´¢T·Ž„ Ü)¬°¶Ç´7¬%YZ–¯Ù_ º#,p©°­ÞfÚ¾ eei¡å~Ý"ZÂw ë…Ú8Ô‘]–¥¤,­užG·Œ^„…°À k}«â‘Ç _ z¼& Ñ¥«÷Z§+=3а¸TXLŠÏÍ –4Œ¡mמ wW§úËpùa[…õ‚Ñ·5^…x|»ODGxgØmxnyûn-!„…°À­Â k±í¼¹ËÞ_¶±¢µ¡wWo°¯/ìÝÕª¨í¨ôè–󼆰¸VXÑR‹\â÷´Ï*¨Fp±£Nƒu½hjk7Ó[ü>2ù 1…°aY;z†jo’}µ ‚Ó Â„¥ðžðt=VGR ÛÛ(eë *ÝÁ} Ÿ³§Ñ~—´{…Ù%„W2a±À…Âzôœµ¡íñ$EW~/á„%;F,´/ñºêߟGx…°aIÏ„§JH½C‰¯ö^!,@X&΄§¢÷Ù ­¼*"¼2#¬;XàFa½xÁiÉKܱ0»,Â=„Ëô™ðÔ±°81o ›+¹6ˆ°À•Âjï [t&<õ¶ðx{²ö)U ¶qaAÚ K÷u[t&<}Q§Ão³¯Ž kW ¸|ÄOoaAj K×÷µåXt&<µÉª°µƒ§Ž¬]ê%£ýc •îâÂÒõÜ@\=fÎû{—mU¤n9« ­)õ#œÂB€”–Þ\¼Ù¢3á©ô¤¡Ì–s¡¿†¬]Ö±ö¸÷± õ…uró²r‡5gÂ3çÂÊ~ËuU™EÖ® «ÈØÍ3.e!@:K×Ù=hÍ™ðti ×҇΃ÇÑ•JxUß9ö¹…é!,]¯é´äLxFYÇk:-ÓUºR ¯ž/>ìŸÈB€t–ÞTu‚Ý• *S"‡3XFÂÒõçÕ£¬UN)'ž*5}+º¹½¬¢)ŠÖ©½æ¸˜:,H+aéðõXt&|ßY‘§JM”fùß*®Rv¥D´Vñ ýX^Â:© Õ(«OuAi‘5Þ-‚ÃaÿÞýõYAª®?Ý õ¬p>¤·>¦.Œm];¬:~gU‡ºJ36Í{”¢ì­T?ØÖJõ/öŠ/° E¸ÿæ³³ U¢,¡UbÕ'E¾Y{GÙkù=™5%my‘([+uzËÔ¿Ô¹7_Æ2€ÔᦠÓÔŸn¯BUVŸ‰  6„Žo¬êðú|•'É­ñyËê–´²Z#ÕQÞÊ E»ÔÚ3ç‚%©ÅW ¢¬ºn+Ï„#\#Ñ¢ÕÕѨ¦iœÿÌ…WÇ)›‡'~šÇRŽE×ÎQWÖ†q£,L“Äð*´_^½AÚ)ɽK¢¬qª²úðF²t% ¯Þžü >¸!Êê;Êzs$)¼úrx5!Ø^=#¯&\Ï#Վeý‹$NWYo)[Ã÷}žçÒ0Êš9¬¼ ¶r&L^ÖÞ;¤üUÝrë"nHCŽÜ4áaõª¬ü<Þ&‰àjõID<Î-gH[žœ"ˆ².˜lÏ{ÂD]¦Þ·uÚükx¨!yùW‚(+óü(‹3¡ýá• Eßú…ïòDQÖ‡”nÿï ^(UÿÿÉ-wrË\À§2Ô£¬Î²³'Û¿€RlEÔ¢ï:Â+p · ¢,OÑ&΄‰ ¯¨ŠÎü)-úÀEQÖµo«ÿ¿|ߤ΄ö‡WÇËÕ¯ Ò¢\Æ//Ÿ©®¬Êµ£6‡W­-ú¾ÍÈApf¬WŸsZü^”Å$y;ˆä«ëj:-ú€(kÜ(«êt”µ»XOuU§zxE‹> ÊR¡iÒ`8ü:s¹,ÏÚŸ¡E€3îDY5΄VÓ:¤žµßG‹> ÊDYK9ZÊ Ÿ EßhÑpÑíSÔMy7 ­#X§~ËyæÝ´è8Å+®®¬'TZ^mT¿åü0-ú>Œ²’DY­ Uµ P4OÒ¢ïIžQ“Q–¿d/ ã,=Q)¸å|-áÀy<)‰²ž"ÊŠ‡ÞA‹¾ËïåḀ×& ¢¬Æ ¢,ÓáÕ2õYÎÓ&ÜÄ“ w”Õïkå\h*¼ªØ&hÑ÷}žJ€Q¹GÐ+Ë_¿kÿHëÖý‚ðêÖŸðHŒÁ‘«îPoHzì¢,™®z— ZôÝL‹>K£¬ƒDYª«üê…¢óiÑ e .y¨ÊR ¯Ö¨·è;üCZô¨ò]A”•]DF*·œ-úÞ`¾€MQÖª=ôpˆW}–óôw¯d¼QÖè…¢{-ú&~'Àß“DYUDY#—2dmUoÑwó%eer.¼PW 5‚}e¾@<¼ôÎt¢,óZô Ó¢ ~®žªe5—Eˆ²Î¾å¬Þ¢oZ-ú¬à¶‰êQ–§«Q½Ë9Sý–óZôXeÝu‡ú¹0“ʧh-UÿÌ£E€¥Q– *«2äúÆ3±ÞbõÏëËɼ(«#âneE—¨ßrž6á&îáXÎwmß=\eiõ}à ?Ï“`GïºB0ÙÞ­gZB¢}‹x®œeÕ¸2ʊТ 5£¬º^·õP®®Ürž πͼ;Q=Ê*ßèªÆ3ÑÝê·œ/üÏ@¢¬7QÖó´¬Y‚}—0_ AQÖu‚(ËçŽÉö±¯z_é¹´èH ×¢,I¯ n9× Â«¯Ñ¢ ÁQÖ‰²> ¯6Ò¢ }¢¬Æ<-ë·-ú \&ˆ²:}'Ò4ÊŠµæ Zô½óÏ @*DYõéØC9ÖÛѯ^Ý}5Û+€‰²¶¥ß8°è‚}÷ð¼¤T”•VçB-pLý–ó”+™/ZQÖPÝr~ìW¯ñ¤8#Êúé4õ(+]ÆEòÕu5û!Zô8‡-\«¼z”ãÀª«”[ô O›À| G±èA”µ8ÕÏDŸ+WŸ/qßí<Ž‹²n~@=ÊZžÊ=”E·œoa¾€#ùú|I”•ª=”c >A‹¾Çïå¹Hƒ(ëØš”Œ²‚%ê…¢Óæÿ1Ï@šDY¡”+qÜr>¼ðßy Ò(Ê*ޤTe­Bý–³~Å%Ì—p>ÿ$ˆ²²»úR'¼:Q©®«é×Ñ¢ E¢¬ªGY«Ž§F”Õ²Ký–óð̯1_ u¢¬;Sߌ”¦B”U½3[ТïÓ<©ÄýK¢,§÷PÖŽ·Ó¢ ùcQ”åäÆ3Zhÿz‹¾O^¤"ÿ.ˆ²ÚeÅD·œÆ| €T²nDY•ÎŒ²ª«:áÕ—ùÒR8ʺ9µ£¬h¡z‹>}Ê] GpQ”u ê°ðê u]Í™|?ß6@ÊóÁÃLEYõYÎú?û:ß4€ë¢¬\‡L¶ë·œ3þ¯ }¢¬ÙÊ‹¿¿Ã QVt’ú-çõS.¡ç€[£,OÒ«²´Š­‚ðê“ô¼H7þú¾ae4U$3ÊjiÌržý—¿à»HÃ(ëZA”U“¼(«wž`–sÆaÜ QÖêäDYÕËÔo9þÂ+€4æú ‚(ë©ÄGYZ`•`¾Ä'™6@”•¬(+*Ì—øK¶¤=¯ýjŽ3«²b»òÕ›2̼øOù*ÜÀ/W²:e%·œ/½ä¾G—ðI”µ1QVtÍߨ·èû*ါ¸}ŠúÃGl²´¬êÇÔ·/§ò Àe¼|í§Te 4n9Ïžð|yîã†ËÕ£¬æÛ¢¬^É-ç…W2m€(k<öÚeIn9¯ýó?¡a;Q– ³lˆ²´Š&Á-çËiØàj^‘TeùNXeµ4Ô«ùŸâëp;3’eÅzç©ëjÚÄßsÉ.ºèAA”µÍ²(KrËy˜}ð>’(ë`–QV4 >ËYìKÌ—€EYÞ¸«²´P¥d¾…¢p3R²üõqEY‘2õa‰3'~œð ÎçÁŒ‡e W6«Ï—øÌ_Qy#q¥$ÊÊ3eõnT^}„iƒ0 ¯Š¢¬†˜‰[ÎÏ Â«/þ) Û`tfÜ=S=Ê* u%¹å<óÒ»èycsO†zUÖI”ÛU¯®«ÃŸùÒ ¾ —«QV£êÃXµà–³>ç¿r”xÕúª¬hÅ[‚ùÿMd@I”Õ\¿P4W^ý¥ Ae[V=fS†H±$¼úíñ逌£’(ëàñQ£¬Xð€_ÝWÿð‘›øè@ÎKïLDY¡‘£¬¾5‚[Îsÿ•¬Lò IUV]ÃÀ…Y{–`–óì è âà¶ŒõÊÂi_<¿P4_]Wë/½ò(Ÿ7ÄÑ«¦¨Kgqà¬BÒØº"õÑóú-¿½—âå¥Ésmßßoïל´WýMÿWz^€%\-ˆ²²;Z[N…Wy‚ð*ãã|È`÷ze­ZÒ}Ó'˜/qé%¯ñ €u½ëuíLïZûÕ/]ÍÇ Ö"в”yûÿÞûA°I”¥ÆßgüÝOø\À¾,ˆ²T«¿b|ØÆÑ»®°JWßý¥ `+/½cM”5ý‹¿ˆ†í`3×XeýýÄ/Pʉà݉ñEYÿËogðnÃO.‰#Êþ‡\ÏG‰ã²ëÌFYs¿øñEl¯ ¡\=ÕL”EÃvH ßÍ8,ÔÕáÏ|‰ÙóŽH.žž6x$‹W `Ú $Õ †„WàTÚ¾–&2à®/Êšó—ÌǰècM0œ}ñïi"ŽáÈ w2Ù¾sù¿\ò8Šïd ”µÿÿyŸ 8m“uäªó/;øS( Žäè­ÓÏí(ú‘OQyNÝe]öøUYk?ûÿ~Ï%gp2ÿ4áÌÃ9_üÂË|àìMÖOÞ½O×ç^üêÚ ”uäÿ\þ¯ð9@zñÿ:É‹]þ|¿ÓIEND®B`‚././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/_static/site.webmanifest0000644000175100001660000000046715003227510021047 0ustar00runnerdocker{ "name": "Streamlink documentation", "short_name": "Streamlink", "display": "standalone", "theme_color": "#121657", "background_color": "#ffffff", "icons": [ { "src": "/_static/icon.svg", "sizes": "1x1", "type": "image/svg" } ] } ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745694559.9428184 streamlink-7.3.0/docs/_static/styles/0000755000175100001660000000000015003227540017174 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/_static/styles/custom.css0000644000175100001660000001717415003227510021227 0ustar00runnerdocker/* Furo CSS variables https://github.com/pradyunsg/furo/blob/main/src/furo/assets/styles/variables/_index.scss https://github.com/pradyunsg/furo/blob/main/src/furo/theme/partials/_head_css_variables.html */ body, body[data-theme="auto"], body[data-theme="light"], body[data-theme="dark"] { /* adjust font-stack by adding "SF Pro Display" and "SF Mono" */ --font-stack: -apple-system, BlinkMacSystemFont, SF Pro Display, Segoe UI, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji; --font-stack--monospace: SFMono-Regular, SF Mono, Menlo, Consolas, Monaco, Liberation Mono, Lucida Console, monospace; /* Streamlink colors */ --color-brand-primary: rgb(18, 22, 87); /* dark icon color (shadow icon color: rgb(13, 16, 65)) */ --color-brand-content: rgb(0, 115, 189); /* bright icon color */ /* misc */ --sidebar-width: 15rem; --sidebar-scrollbar-width: .5rem; --sidebar-item-spacing-vertical: .4rem; --admonition-font-size: var(--font-size--small); --admonition-title-font-size: var(--font-size--normal); --custom-thumb-color: var(--color-foreground-border); --custom-track-color: transparent; } body[data-theme="dark"] { --color-brand-primary: rgb(0, 115, 189); /* bright icon color */ --color-brand-content: rgb(0, 115, 189); /* bright icon color */ } @media (prefers-color-scheme: dark) { body[data-theme="auto"] { --color-brand-primary: rgb(0, 115, 189); /* bright icon color */ --color-brand-content: rgb(0, 115, 189); /* bright icon color */ } } /* Generic style overrides */ html { /* unset @media (min-width: $full-width + $sidebar-width) query which sets font-size to 110% */ font-size: 100% !important; } h1, h2 { margin-top: 1.5rem; margin-bottom: 0.75rem; font-weight: 300; } h3, h4, h5, h6 { margin-top: 1.5rem; margin-bottom: 0.5rem; font-weight: 400; } h1 { font-size: 2.25rem; } h2 { font-size: 1.75rem; } h3 { font-size: 1.25rem; } h4 { font-size: 1rem; } h5 { font-size: 0.875rem; } h6 { font-size: 0.75rem; } code.literal { font-size: var(--font-size--small); } code.literal.xref.std-option { white-space: nowrap; } strong.command { padding: .1em .2em; border-radius: .2em; background: var(--color-background-secondary); color: var(--color-api-name); font: normal var(--font-size--small) var(--font-stack--monospace); } .highlight .go { font-style: normal; } a[href^="http://"]:not(.muted-link):not(.sd-badge), a[href^="https://"]:not(.muted-link):not(.sd-badge):not([href^="https://streamlink.github.io/"]) { display: inline-block; word-break: break-word; } a[href^="http://"]:not(.no-external-link-icon):not(.muted-link):not(.sd-badge)::after, a[href^="https://"]:not(.no-external-link-icon):not(.muted-link):not(.sd-badge):not([href^="https://streamlink.github.io/"])::after { content: "\f35d"; display: inline-block; padding-left: .4em; font: 900 .6em "Font Awesome 6 Free"; vertical-align: middle; text-decoration: none; } /* Sidebar/Menubar and related */ .sidebar-scroll, .toc-scroll { overflow: hidden auto; } .toc-scroll:not(:hover) { scrollbar-color: transparent !important; } .toc-scroll:not(:hover)::-webkit-scrollbar-thumb { background-color: transparent !important; } .sidebar-brand, .sidebar-versions, .sidebar-search, .github-buttons { box-sizing: border-box; width: calc(var(--sidebar-width) - 2 * var(--sidebar-scrollbar-width)) !important; margin-left: var(--sidebar-scrollbar-width) !important; margin-right: 0 !important; } .toc-title, .toc-tree > ul { padding-right: .5em; } .sidebar-logo-container { margin: 0; } .sidebar-logo { max-width: 62.5%; } .sidebar-brand { color: var(--color-sidebar-brand-text); } .sidebar-brand-text { font-size: 1.75rem; color: unset; } .sidebar-brand-oneliner { margin: 0; font-size: var(--font-size--small--2); color: unset; } .sidebar-versions { margin: .5rem 0; } .sidebar-versions a { color: inherit; } .sidebar-versions-current { font-family: var(--font-stack--monospace); font-size: var(--font-size--small--2); } .sidebar-versions-others { display: flex; margin: .5rem 0 0; font-size: var(--font-size--small); } .sidebar-versions-others dd { width: 50%; margin: 0 !important; } .sidebar-versions-others dd:first-of-type { padding-right: .5rem; text-align: right; } .sidebar-versions-others dd:last-of-type { padding-left: .5rem; text-align: left; } .sidebar-versions-others .version-current { font-weight: bold; } .sidebar-search-container { border-top: 1px solid var(--color-sidebar-search-border); border-bottom: 1px solid var(--color-sidebar-search-border); } .sidebar-search { border: 0; padding-left: calc( var(--sidebar-item-spacing-horizontal) + var(--sidebar-search-input-spacing-horizontal) + var(--sidebar-search-icon-size) - var(--sidebar-scrollbar-width) ); } .github-buttons { margin: 1.5rem 0 1rem; } .toc-tree li.scroll-current > .reference { position: relative; font-weight: normal; } .toc-tree li.scroll-current > .reference::before { content: "\2023"; display: inline-block; position: absolute; left: -1em; width: 1em; color: var(--color-toc-item-text); font: normal normal 400 1em var(--font-stack); text-rendering: auto; -webkit-font-smoothing: antialiased; opacity: .75; } /* Components */ .admonition p.admonition-title { font-weight: bold; } .admonition.version-warning { padding-bottom: 0; } .admonition.version-warning > .admonition-title { margin-bottom: 0; font-weight: normal; } table.table-custom-layout { width: 100%; table-layout: fixed; } table.table-custom-layout colgroup { display: none; } table.table-custom-layout colgroup col { width: auto; } table.table-custom-layout th { text-align: left; } table.table-custom-layout th:first-of-type { width: 15rem; } table.table-custom-layout tbody tr td { overflow: unset; white-space: unset; } table.table-custom-layout tbody tr td:first-of-type { vertical-align: top; } table.table-custom-layout.table-custom-layout-platform-locations th:first-of-type { width: 7rem; } table.table-custom-layout.table-custom-layout-platform-locations tbody>tr>td:last-of-type { overflow-x: auto; } table.table-custom-layout.table-custom-layout-platform-locations ul { margin: 0; padding: 0; list-style: none; } table.table-custom-layout.table-custom-layout-platform-locations code { white-space: pre; } table.table-custom-layout.table-custom-layout-dependencies th:nth-of-type(1) { width: 5rem; } table.table-custom-layout.table-custom-layout-dependencies th:nth-of-type(2) { width: 12rem; } table.table-custom-layout.table-custom-layout-dependencies tbody td { vertical-align: top; } .option .sig-name, .option .sig-prename { font-family: var(--font-stack--monospace); } /* Content */ .installation-grid .fab { font-size: 5em; } .grid-with-icons .sd-card-header .fas { width: 1em; margin-left: -1.25em; margin-right: .25em; } .grid-with-icons .sd-card-body .fas { padding-right: 0.1em; vertical-align: middle; } .grid-with-images img { width: auto; max-height: 3em; filter: grayscale(1); } .team-members img { width: 100px; height: 100px; margin: 0 auto; } .team-members a:not(.sd-hide-link-text)::after { display: none !important; } section#plugins dl.field-list { margin: 0 0 0 1rem; } section#plugins dl.field-list > dt { float: left; width: 8rem; margin: 0; } section#plugins dl.field-list > dd { margin: 0 0 0 8rem; } section#plugins dl.field-list ul, section#plugins dl.field-list p { margin: 0; padding: 0; } section#plugins dl.field-list li { list-style: none; } /* Utils */ .clearfix:before, .clearfix:after { display: table; content: ""; } .clearfix:after { clear: both; } ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745694559.9438186 streamlink-7.3.0/docs/_templates/0000755000175100001660000000000015003227540016360 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/_templates/base.html0000644000175100001660000000175415003227510020164 0ustar00runnerdocker{% extends '!base.html' %} {%- block site_meta -%} {{ super() }} {% if not docstitle -%} {%- elif pagename == master_doc -%} {%- else -%} {%- endif %} {%- endblock -%} ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/_templates/page.html0000644000175100001660000000046215003227510020161 0ustar00runnerdocker{% extends '!page.html' %} {% block content %} {% if release != version %}

You are reading the documentation for the in-development version of Streamlink.

{% endif %} {{ super() }} {% endblock %} ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745694559.9438186 streamlink-7.3.0/docs/_templates/sidebar/0000755000175100001660000000000015003227540017771 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/_templates/sidebar/brand.html0000644000175100001660000000161415003227510021744 0ustar00runnerdocker ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/_templates/sidebar/github-buttons.html0000644000175100001660000000100715003227510023630 0ustar00runnerdocker ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745694559.9458184 streamlink-7.3.0/docs/api/0000755000175100001660000000000015003227540014774 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/api/cache.rst0000644000175100001660000000010015003227510016555 0ustar00runnerdockerCache ----- .. module:: streamlink.cache .. autoclass:: Cache ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/api/exceptions.rst0000644000175100001660000000075015003227510017706 0ustar00runnerdockerExceptions ---------- .. autoexception:: streamlink.exceptions.StreamlinkError .. autoexception:: streamlink.exceptions.PluginError .. autoexception:: streamlink.exceptions.FatalPluginError .. autoexception:: streamlink.exceptions.NoPluginError .. autoexception:: streamlink.exceptions.NoStreamsError .. autoexception:: streamlink.exceptions.StreamError .. autoexception:: streamlink.webbrowser.exceptions.WebbrowserError .. autoexception:: streamlink.webbrowser.cdp.exceptions.CDPError ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/api/options.rst0000644000175100001660000000022115003227510017211 0ustar00runnerdockerOptions ------- .. module:: streamlink.options .. autoclass:: Options :private-members: .. autoclass:: Argument .. autoclass:: Arguments ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/api/plugin.rst0000644000175100001660000000046315003227510017024 0ustar00runnerdockerPlugin ------ .. module:: streamlink.plugin .. autoclass:: Plugin :private-members: _get_streams :member-order: bysource Plugin decorators ^^^^^^^^^^^^^^^^^ .. autodecorator:: pluginmatcher .. autodecorator:: pluginargument .. autodata:: streamlink.plugin.plugin._PLUGINARGUMENT_TYPE_REGISTRY ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/api/session.rst0000644000175100001660000000055315003227510017211 0ustar00runnerdockerSession ------- .. autoclass:: streamlink.session.Streamlink :member-order: bysource .. autoclass:: streamlink.session.options.StreamlinkOptions .. automethod:: get .. automethod:: set .. autoclass:: streamlink.session.plugins.StreamlinkPlugins :member-order: bysource :special-members: __getitem__, __setitem__, __delitem__, __contains__ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/api/stream.rst0000644000175100001660000000041115003227510017012 0ustar00runnerdockerStream ------ .. module:: streamlink.stream All streams inherit from the :class:`Stream` base class. .. autoclass:: Stream .. autoclass:: MuxedStream .. autoclass:: HTTPStream .. autoclass:: HLSStream .. autoclass:: MuxedHLSStream .. autoclass:: DASHStream ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/api/streamlink.rst0000644000175100001660000000007415003227510017675 0ustar00runnerdockerStreamlink ---------- .. autofunction:: streamlink.streams ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/api/validate.rst0000644000175100001660000002640415003227510017322 0ustar00runnerdockerValidation schemas ------------------ .. .. Sphinx's autodoc doesn't properly document imported module members and it just outputs "alias of" for re-exported classes. This means we'll have to run `automodule` twice if we want to document the original classes: 1. the main public interface (which contains aliases, like `all` for `AllSchema` for example) 2. the original schema classes with their full signatures and docstrings . Ignore unneeded classes like `SchemaContainer` which are not useful for the API docs. . Ignore `validate` as well, as `functools.singledispatch` functions are not fully supported by autodoc. Instead, manually document `validate` and its overloading functions for base schema types here at the top, just below the manually imported `Schema` (the main validation schema interface). The documentations for any custom schemas like `AllSchema` for example is done on the schemas themselves. . Ideally, we'd just run autodoc on the main module and configure the order of items. :( Please see the :ref:`validation schema guides ` for an introduction to this API and a list of examples. .. admonition:: Public interface :class: caution While the internals are implemented in the ``streamlink.validate`` package, :ref:`streamlink.plugin.api.validate ` provides the main public interface for plugin implementors. .. autoclass:: streamlink.plugin.api.validate.Schema :members: :undoc-members: .. py:function:: validate(schema, value) :module: streamlink.plugin.api.validate The core of the :mod:`streamlink.plugin.api.validate` module. It validates the given input ``value`` and returns a value according to the specific validation rules of the ``schema``. If the validation fails, a :exc:`ValidationError <_exception.ValidationError>` is raised with a detailed error message. The ``schema`` can be any kind of object. Depending on the ``schema``, different validation rules apply. Simple schema objects like ``"abc"`` or ``123`` for example test the equality of ``value`` and ``schema`` and return ``value`` again, while type schema objects like ``str`` test whether ``value`` is an instance of ``schema``. ``schema`` objects which are callable receive ``value`` as a single argument and must return a truthy value, otherwise the validation fails. These are just a few examples. The ``validate`` module implements lots of special schemas, like :class:`validate.all ` or :class:`validate.any ` for example, which are schema containers that receive a sequence of sub-schemas as arguments and where each sub-schema then gets validated one after another. :class:`validate.all ` requires each sub-schema to successfully validate. It passes the return value of each sub-schema to the next one and then returns the return value of the last sub-schema. :class:`validate.any ` on the other hand requires at least one sub-schema to be valid and returns the return value of the first valid sub-schema. Any validation failures prior will be ignored, but at least one must succeed. Other special ``schema`` cases for example are instances of sequences like ``list`` or ``tuple``, or mappings like ``dict``. Here, each sequence item or key-value mapping pair is validated against the input ``value`` and a new sequence/mapping object according to the ``schema`` validation is returned. :func:`validate()` should usually not be called directly when validating schemas. Instead, the wrapper method :meth:`Schema.validate() ` of the main :class:`Schema` class should be called. Other Streamlink APIs like the methods of the :class:`HTTPSession ` or the various :mod:`streamlink.utils.parse` functions for example expect this interface when the ``schema`` keyword is set, which allows for immediate validation of the data using a :class:`Schema` object. :func:`validate()` is implemented using the stdlib's :func:`functools.singledispatch` decorator, where more specific schemas overload the default implementation with more validation logic. ---- By default, :func:`validate()` compares ``value`` and ``schema`` for equality. This means that simple schema objects like booleans, strings, numbers, None, etc. are validated here, as well as anything unknown. Example: .. code-block:: python schema = validate.Schema(123) assert schema.validate(123) == 123 assert schema.validate(123.0) == 123.0 schema.validate(456) # raises ValidationError schema.validate(None) # raises ValidationError :param Any schema: Any kind of object not handled by a more specific validation function :param Any value: The input value :raise ValidationError: If ``value`` and ``schema`` are not equal :return: Unmodified ``value`` .. py:function:: _validate_type(schema, value) :module: streamlink.plugin.api.validate :class:`type` validation. Checks if ``value`` is an instance of ``schema``. Example: .. code-block:: python schema = validate.Schema(int) assert schema.validate(123) == 123 assert schema.validate(True) is True # `bool` is a subclass of `int` schema.validate("123") # raises ValidationError *This function is included for documentation purposes only! (singledispatch overload)* :param type schema: A :class:`type` object :param Any value: The input value :raise ValidationError: If ``value`` is not an instance of ``schema`` :return: Unmodified ``value`` .. py:function:: _validate_callable(schema, value) :module: streamlink.plugin.api.validate ``Callable`` validation. Validates a ``schema`` function where ``value`` gets passed as a single argument. Must return a truthy value. Example: .. code-block:: python schema = validate.Schema( lambda val: val < 2, ) assert schema.validate(1) == 1 schema.validate(2) # raises ValidationError *This function is included for documentation purposes only! (singledispatch overload)* :param Callable schema: A function with one argument :param Any value: The input value :raise ValidationError: If ``schema`` returns a non-truthy value :return: Unmodified ``value`` .. py:function:: _validate_sequence(schema, value) :module: streamlink.plugin.api.validate :class:`list `, :class:`tuple`, :class:`set` and :class:`frozenset` validation. Each item of ``value`` gets validated against **any** of the items of ``schema``. Please note the difference between :class:`list ` and the :class:`ListSchema <_schemas.ListSchema>` validation. Example: .. code-block:: python schema = validate.Schema([1, 2, 3]) assert schema.validate([]) == [] assert schema.validate([1, 2]) == [1, 2] assert schema.validate([3, 2, 1]) == [3, 2, 1] schema.validate({1, 2, 3}) # raises ValidationError schema.validate([1, 2, 3, 4]) # raises ValidationError *This function is included for documentation purposes only! (singledispatch overload)* :param Union[list, tuple, set, frozenset] schema: A sequence of validation schemas :param Any value: The input value :raise ValidationError: If ``value`` is not an instance of the ``schema``'s own type :return: A new sequence of the same type as ``schema`` with each item of ``value`` being validated .. py:function:: _validate_dict(schema, value) :module: streamlink.plugin.api.validate :class:`dict` validation. Each key-value pair of ``schema`` gets validated against the respective key-value pair of ``value``. Additional keys in ``value`` are ignored and not included in the validation result. If a ``schema`` key is an instance of :class:`OptionalSchema <_schemas.OptionalSchema>`, then ``value`` may omit it. If one of the ``schema``'s keys is a :class:`type`, :class:`AllSchema <_schemas.AllSchema>`, :class:`AnySchema <_schemas.AnySchema>`, :class:`TransformSchema <_schemas.TransformSchema>`, or :class:`UnionSchema <_schemas.UnionSchema>`, then all key-value pairs of ``value`` are validated against the ``schema``'s key-value pair. Example: .. code-block:: python schema = validate.Schema({ "key": str, validate.optional("opt"): 123, }) assert schema.validate({"key": "val", "other": 123}) == {"key": "val"} assert schema.validate({"key": "val", "opt": 123}) == {"key": "val", "opt": 123} schema.validate(None) # raises ValidationError schema.validate({}) # raises ValidationError schema.validate({"key": 123}) # raises ValidationError schema.validate({"key": "val", "opt": 456}) # raises ValidationError .. code-block:: python schema = validate.Schema({ validate.any("a", "b"): int, }) assert schema.validate({}) == {} assert schema.validate({"a": 1}) == {"a": 1} assert schema.validate({"b": 2}) == {"b": 2} assert schema.validate({"a": 1, "b": 2}) == {"a": 1, "b": 2} schema.validate({"a": 1, "b": 2, "other": 0}) # raises ValidationError schema.validate({"a": None}) # raises ValidationError *This function is included for documentation purposes only! (singledispatch overload)* :param dict schema: A :class:`dict` :param Any value: The input value :raise ValidationError: If ``value`` is not a :class:`dict` :raise ValidationError: If any of the ``schema``'s non-optional keys are not part of the input ``value`` :return: A new :class:`dict` .. py:function:: _validate_pattern(schema, value) :module: streamlink.plugin.api.validate :class:`re.Pattern` validation. Calls the :meth:`re.Pattern.search()` method on the ``schema`` pattern. Please note the difference between :class:`re.Pattern` and the :class:`RegexSchema <_schemas.RegexSchema>` validation. Example: .. code-block:: python schema = validate.Schema( re.compile(r"^Hello, (?P\w+)!$"), ) assert schema.validate("Does not match") is None assert schema.validate("Hello, world!")["name"] == "world" schema.validate(123) # raises ValidationError schema.validate(b"Hello, world!") # raises ValidationError *This function is included for documentation purposes only! (singledispatch overload)* :param re.Pattern schema: A compiled :class:`re.Pattern` object (:func:`re.compile()` return value) :param Any value: The input value :raise ValidationError: If ``value`` is not an instance of :class:`str` or :class:`bytes` :raise ValidationError: If the type of ``value`` doesn't match ``schema``'s :class:`str`/:class:`bytes` type :return: ``None`` if ``value`` doesn't match ``schema``, or the resulting :class:`re.Match` object .. automodule:: streamlink.plugin.api.validate :imported-members: :exclude-members: Schema, SchemaContainer, validate :member-order: bysource .. automodule:: streamlink.validate._schemas :exclude-members: SchemaContainer :member-order: bysource :no-show-inheritance: .. autoexception:: streamlink.validate._exception.ValidationError ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/api/webbrowser.rst0000644000175100001660000000134615003227510017710 0ustar00runnerdockerWebbrowser ---------- .. warning:: The APIs of the ``streamlink.webbrowser`` package are considered unstable. Use at your own risk! .. module:: streamlink.webbrowser .. autoclass:: streamlink.webbrowser.cdp.client.CDPClient .. autoclass:: streamlink.webbrowser.cdp.client.CDPClientSession .. autoclass:: streamlink.webbrowser.cdp.client.CMRequestProxy .. autoclass:: streamlink.webbrowser.cdp.connection.CDPBase .. autoclass:: streamlink.webbrowser.cdp.connection.CDPConnection .. autoclass:: streamlink.webbrowser.cdp.connection.CDPSession .. autoclass:: streamlink.webbrowser.cdp.connection.CDPEventListener .. autoclass:: streamlink.webbrowser.webbrowser.Webbrowser .. autoclass:: streamlink.webbrowser.chromium.ChromiumWebbrowser ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/api.rst0000644000175100001660000000040315003227510015520 0ustar00runnerdockerAPI Reference ============= This is an incomplete reference of the relevant Streamlink APIs. .. toctree:: api/streamlink api/session api/plugin api/options api/cache api/validate api/stream api/webbrowser api/exceptions ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745694559.9458184 streamlink-7.3.0/docs/api_guide/0000755000175100001660000000000015003227540016151 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/api_guide/quickstart.rst0000644000175100001660000001427515003227510021103 0ustar00runnerdockerQuickstart ---------- This API is what powers the :ref:`CLI `, but it's also available to developers that wish to make use of the data Streamlink can retrieve in their own application. Extracting streams ^^^^^^^^^^^^^^^^^^ The simplest use of the Streamlink API looks like this: .. code-block:: python >>> import streamlink >>> streams = streamlink.streams("hls://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_4x3/bipbop_4x3_variant.m3u8") This simply attempts to find a plugin and use it to extract streams from the URL. This works great in simple cases but if you want more fine tuning you need to use a `session object`_ instead and `fetch streams `_ manually. The returned value is a dict containing :py:class:`Stream ` objects: .. code-block:: python >>> streams {'41k': , '230k': , '650k': , '990k': , '1900k': , 'worst': , 'best': } If no plugin for the URL is found, a :py:exc:`NoPluginError ` will be raised. If an error occurs while fetching streams, a :py:exc:`PluginError ` will be raised. Opening streams to read data ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Now that you have extracted some streams we might want to read some data from one of them. When you call :py:meth:`open() ` on a stream, a file-like object will be returned, which you can call ``.read(size)`` and ``.close()`` on. .. code-block:: python >>> fd = streams["best"].open() >>> data = fd.read(1024) >>> fd.close() If an error occurs while opening a stream, a :py:exc:`StreamError ` will be raised. Inspecting streams ^^^^^^^^^^^^^^^^^^ It's also possible to inspect the stream's internal parameters. Go to :ref:`Stream subclasses ` to see which attributes are available for inspection for each stream type. For example, this is an :py:class:`HLSStream ` object which contains a :py:attr:`url ` attribute. .. code-block:: python >>> streams["best"].url 'https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_4x3/gear4/prog_index.m3u8' Session object ^^^^^^^^^^^^^^ The session allows you to set various options and is more efficient when extracting streams more than once. You start by creating a :py:class:`Streamlink ` object: .. code-block:: python >>> from streamlink import Streamlink >>> session = Streamlink({"optional-session-option": 123}) On the session instance, you can set additional options like so: .. code-block:: python >>> session.set_option("stream-timeout", 30) >>> session.options.set("stream-timeout", 30) See :py:class:`StreamlinkOptions ` for all available session options. Fetching streams ^^^^^^^^^^^^^^^^ Streams can be fetched in two different ways. The first example will automatically try to find a matching plugin and available streams from the input URL: .. code-block:: python >>> streams = session.streams("URL") See :py:meth:`Streamlink.streams() ` for more. ``Streamlink.streams()`` however doen't allow you to set any plugin options which might be necessary in order to access streams, e.g. when authentication data is required, or plugin options which may alter the plugin's behavior. Be aware that plugin options are distinct from the session options, and since these options depend on the plugin in use, plugin options can't be set without resolving the matching plugin first. Plugins can therefore be resolved and initialized manually from the input URL, so plugin options can be passed to the plugin: .. code-block:: python >>> plugin_name, plugin_class, resolved_url = session.resolve_url("URL") >>> plugin = plugin_class(session, resolved_url, options={"plugin-option": 123}) >>> streams = plugin.streams() See :py:meth:`Streamlink.resolve_url() ` and :py:class:`Plugin ` for more. Alternatively, the plugin class can be imported directly from the respective module of the ``streamlink.plugins`` package. The input URL then must match the plugin's URL matchers. .. code-block:: python >>> from streamlink.plugins.twitch import __plugin__ as Twitch >>> plugin = Twitch(session, "https://twitch.tv/CHANNEL", options={"disable-ads": True, "low-latency": True}) >>> streams = plugin.streams() Available plugin options are defined using the :py:meth:`@pluginargument ` Plugin class decorator in each plugin's module. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/api_guide/validate.rst0000644000175100001660000004232515003227510020477 0ustar00runnerdockerValidation schemas ================== .. currentmodule:: streamlink.plugin.api.validate Introduction ------------ The :ref:`streamlink.plugin.api.validate ` module provides an API for defining declarative validation schemas which are used to verify and extract data from various inputs, for example HTTP responses. Validation schemas are a powerful tool for :ref:`plugin ` implementors to find and extract data like stream URLs, stream metadata and more from websites and web APIs. Instead of verifying and extracting data programatically and having to perform error handling manually, declarative validation schemas allow defining comprehensive validation and extraction rules which are easy to understand and which raise errors with meaningful messages upon extraction failure. .. admonition:: Public interface :class: caution While the internals are implemented in the ``streamlink.validate`` package, :ref:`streamlink.plugin.api.validate ` provides the main public interface for plugin implementors. Examples -------- Simple schemas ^^^^^^^^^^^^^^ Let's begin with a few simple validation schemas which are not particularly useful yet. .. code-block:: pycon >>> from streamlink.plugin.api import validate >>> schema_one = validate.Schema("123") >>> schema_two = validate.Schema(123) >>> schema_three = validate.Schema(int, 123.0) >>> schema_one.validate("123") '123' >>> schema_two.validate(123) 123 >>> schema_three.validate(123) 123 First, three :class:`Schema` instances are created, ``schema_one``, ``schema_two`` and ``schema_three``. The :class:`Schema` class is the main schema validation interface and the outer wrapper for all schema definitions. It is a subclass of :class:`validate.all ` which additionally implements the :meth:`Schema.validate()` method. This interface is expected by various Streamlink methods and functions when passing the ``schema`` argument/keyword, for example to the :class:`HTTPSession ` methods or :mod:`streamlink.utils.parse` functions. The :class:`validate.all ` class takes a sequence of schema object arguments and validates each one in order. All schema objects in this schema container must be valid. Schema objects can be anything, and depending on their type, different validations will be applied. In our example, both ``schema_one`` and ``schema_two`` contain only one schema object, namely ``"123"`` and ``123`` respectively, whereas ``schema_three`` contains two schema objects, ``int`` and ``123.0``. This means that the first two schemas validate only one condition, while the third one validates two, first ``int``, then ``123.0``. As you've probably already noticed, validation schemas also have a return value for their extraction purpose, but this isn't much interesting in this example. The ``"123"``, ``123`` and ``123.0`` schemas are simple :func:`equality validations `. This is the case for all basic objects, and all they do is validate and return the input value again. ``int`` however is a ``type`` object, and thus a :func:`type validation <_validate_type>`, which checks whether the input is an instance of the schema object and then also returns the input value again. Since ``123`` is an ``int``, the schema is valid for that input. ``schema_three`` however hasn't finished validating yet at this point, as it defines two validation schemas in total. This means that the return value of the ``int`` validation gets passed to the ``123.0`` schema validation, and as expected when checking ``123 == 123.0``, despite both the input and schema being different types, namely ``int`` and ``float``, the validation succeeds and returns its input value again, causing the return value of the whole ``schema_three`` to be ``123``. Now let's have a look at validation errors. .. code-block:: pycon >>> schema_one.validate(123) streamlink.exceptions.PluginError: Unable to validate result: ValidationError(equality): 123 does not equal '123' >>> schema_three.validate(123.0) streamlink.exceptions.PluginError: Unable to validate result: ValidationError(type): Type of 123.0 should be int, but is float The first :meth:`Schema.validate()` call passes ``123`` to ``schema_one``. ``schema_one`` however expects ``"123"``, so a :class:`ValidationError ` is raised because the input value is not equal to the schema. :meth:`Schema.validate()` catches the error and wraps it in a :class:`PluginError ` with a specific validation message. The second validation also fails, but here, it's because of the input type. The first sub-schema explicitly checks for the type ``int``, and despite the following schema being ``123.0``, which is a ``float`` object that would obviously validate a ``123.0`` ``float`` input when comparing equality, a :class:`ValidationError ` is raised. Extracting JSON data ^^^^^^^^^^^^^^^^^^^^ The next example shows how to read an optional integer value from JSON data. .. code-block:: pycon >>> from streamlink.plugin.api import validate >>> json_schema = validate.Schema( ... str, ... validate.parse_json(), ... { ... "status": validate.any(None, int), ... }, ... validate.get("status"), ... ) >>> json_schema.validate("""{"status":null}""") None >>> json_schema.validate("""{"status":123}""") 123 >>> json_schema.validate("""Not JSON""") streamlink.exceptions.PluginError: Unable to validate result: ValidationError: Unable to parse JSON: Expecting value: line 1 column 1 (char 0) ('Not JSON') >>> json_schema.validate("""{"status":"unknown"}""") streamlink.exceptions.PluginError: Unable to validate result: ValidationError(dict): Unable to validate value of key 'status' Context(AnySchema): ValidationError(equality): 'unknown' does not equal None ValidationError(type): Type of 'unknown' should be int, but is str Once again, we start with a new :class:`Schema` object which gets assigned to ``json_schema``. This schema collection validates four schemas in total. Each of them must be valid, with each output being the input of the next one. Since our goal is to parse JSON data and extract data from it, this means that we should only accept string inputs, so we set ``str`` as the first schema in this :class:`validate.all ` schema collection. Next is the :func:`validate.parse_json() ` validation, a call of a utility function which returns a :class:`validate.transform ` schema object that does exactly what its name suggests: it takes an input and returns something else. In this case, obviously, strings are the input and a parsed JSON object is the output, assuming that the input is indeed valid JSON data. Now we validate the parsed JSON object. We expect the JSON data to be a JSON ``object``, so we let the next validation schema be a :func:`dict validation <_validate_dict>`. :class:`dict` validation schemas define a set of key-value pairs which must exist in the input, unless keys are set as optional using :class:`validate.optional `. For the sake of simplicity, this isn't the case in this example just yet. Each value of the key-value pairs is a validation schema on its own where the input is validated against. Here, the ``"status"`` key has a :class:`validate.any ` validation schema, which is also a schema collection, similar to :class:`validate.all `, but :class:`validate.any ` requires at least one sub-schema to be valid, not all. Each sub-schema receives the same input, and the output of the overall schema collection is the output of the first sub-schema that's valid. For our example, this means that the value of the ``status`` key in the JSON data must either be ``None`` (``null``) or an ``int``. If any of the schemas in a nested schema definition like that fails, then a validation error stack will be generated by :class:`ValidationError `, as shown above. The last of the four schemas in the outer :class:`validate.all ` schema collection is a :class:`validate.get ` schema. This schema works on any kind of input which implements :func:`__getitem__()`, for example :class:`dict` objects. And as expected, it attempts to get and return the ``"status"`` key of the output of the previous :class:`dict` validation. The :mod:`validation ` module also supports getting multiple values at once using the :class:`validate.union ` or :class:`validate.union_get ` schemas, but this isn't relevant here. Finding stream URLs in HTML ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Let's imagine a simple website where a stream URL is embedded as JSON data in a ``data-player`` attribute of an unknown HTML element where the web player of that website reads from. Extracting this data could be done by using regular expressions, but then we'd have to take HTML syntax into account, as well as JSON syntax which should usually be HTML-encoded in that HTML element attribute, which would make writing a regular expression even harder, apart from the fact that the JSON data structure could easily change at any time. Therefore it would make much more sense parsing the HTML data, querying the resulting node tree using an XPath query for getting the attribute value, then parsing the JSON data and finally finding and validating the stream URL. We also don't want to raise validation errors unnecessarily when the user inputs a URL where no video player was found, so we can instead return an empty list of streams in our plugin implementation and let Streamlink's CLI exit gracefully. Validation errors are only supposed to be raised when an actual error happened due to unexpected data, not when streams are offline or inaccessible. Thanks to validation schemas, we can do all this declaratively without causing a mess when doing this programmatically. .. code-block:: pycon >>> from streamlink.plugin.api import validate >>> schema = validate.Schema( ... validate.parse_html(), ... validate.xml_xpath_string(".//*[@data-player][1]/@data-player"), ... validate.none_or_all( ... validate.parse_json(), ... { ... validate.optional("url"): validate.url( ... path=validate.endswith(".m3u8"), ... ), ... }, ... validate.get("url"), ... ), ... ) >>> schema.validate(""" ... ...
... """) None >>> schema.validate(""" ... ...
... ... ...
... """) None >>> schema.validate(""" ... ...
... ... ...
... """) 'https://host/hls-playlist.m3u8' >>> schema.validate(""" ... ...
... ... ...
... """) streamlink.exceptions.PluginError: Unable to validate result: ValidationError(NoneOrAllSchema): ValidationError(dict): Unable to validate value of key 'url' Context(url): Unable to validate URL attribute 'path' Context(endswith): '/dash-manifest.mpd' does not end with '.m3u8' We start with a new :class:`Schema` and begin by parsing HTML using the :func:`validate.parse_html() ` utility function. Similar to :func:`validate.parse_json() `, it returns a :class:`validate.transform ` schema. :func:`validate.parse_html() ` however returns a parsed HTML node tree via Streamlink's :ref:`lxml dependency `. This is followed by an XPath query schema using the :func:`validate.xml_xpath_string() ` utility function. :func:`validate.xml_xpath_string() ` is a wrapper for :func:`validate.xml_xpath() ` which always returns a string or ``None``, depending on the query result. This is useful for querying text contents or single attribute values, like in this case. XPath queries on their own always return a result set, i.e. possibly multiple values, so when trying to find single values, it is important to limit the number of potential return values to only one in the XPath query. The query here attempts to find any node with a ``data-player`` attribute. It then limits the result set to the first found element and then reads the value of its ``data-player`` attribute. :func:`validate.xml_xpath_string() ` turns this into a single string return value, or ``None`` if no or an empty value was returned by the query. Since we now have two different paths for our overall validation schema, either no player data or still unvalidated player data, our next schema is a :class:`validate.none_or_all ` schema. This works similar to :class:`validate.all `, except that ``None`` inputs are skipped and get returned immediately without validating any sub-schemas. This lets us handle cases where no player was found on the website, without raising a :class:`ValidationError `. In the :class:`validate.none_or_all ` schema, we now attempt to parse JSON data, which was already shown previously, except for the fact that we don't need to validate the ``str`` input here, as the XPath query must have already returned a string value. On to the :func:`dict validation <_validate_dict>`. We're only interested in the ``url`` key. Any other keys of the input will get ignored. Since we're aware that ``url`` can be missing if the stream is offline, we mark it as optional using the :class:`validate.optional ` schema. This makes the :func:`dict validation <_validate_dict>` not raise an error if it's missing, but if it's set, then its value must validate. Talking about the value, we want its value to be a URL. This is where the :func:`validate.url ` utility function comes in handy. It parses the input and lets us validate any parts of the parsed URL with further validation schemas. The return value is always the full URL string. In our example, we want to ensure that the URL's path ends with the ``".m3u8"`` string, which is an indicator for the stream being an HLS stream, so we can pass the URL to Streamlink's :class:`HLS implementation `. Lastly, we simply get the ``url`` key using :class:`validate.get `. The return value must either be ``None`` if no ``url`` key was included in the JSON data, or a ``str`` with a URL where its path ends with ``".m3u8"``. This means that the overall schema can only return ``None`` or said kind of URL string. If the ``url`` key is not a URL, or if its path does not end with ``".m3u8"``, then a :class:`ValidationError ` is raised, which is what we want. The ``None`` return value should then be checked accordingly by the plugin implementation. Validating HTTP responses ^^^^^^^^^^^^^^^^^^^^^^^^^ In order to validate HTTP responses directly, Streamlink's :class:`HTTPSession ` allows setting the ``schema`` keyword in :meth:`HTTPSession.request() `, as well as in each HTTP-verb method like ``get()``, ``post()``, etc. Here's a simple plugin implementation with the same schema from the `Finding stream URLs in HTML`_ example above. .. code-block:: python :caption: example-plugin.py :name: example-plugin import re from streamlink.plugin import Plugin, pluginmatcher from streamlink.plugin.api import validate from streamlink.stream.hls import HLSStream @pluginmatcher(re.compile(r"https://example\.tld/")) class ExamplePlugin(Plugin): def _get_streams(): hls_url = self.session.http.get(self.url, schema=validate.Schema( validate.parse_html(), validate.xml_xpath_string(".//*[@data-player][1]/@data-player"), validate.none_or_all( validate.parse_json(), { validate.optional("url"): validate.url( path=validate.endswith(".m3u8"), ), }, validate.get("url"), ), )) if not hls_url: return None return HLSStream.parse_variant_playlist(self.session, hls_url) __plugin__ = ExamplePlugin ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/api_guide.rst0000644000175100001660000000014415003227510016677 0ustar00runnerdockerAPI Guide ========= .. toctree:: :maxdepth: 2 api_guide/quickstart api_guide/validate ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/applications.rst0000644000175100001660000000153315003227510017442 0ustar00runnerdockerStreamlink Applications ======================= Streamlink Twitch GUI --------------------- .. image:: https://user-images.githubusercontent.com/467294/199009198-011d23f2-a884-4bf8-94d0-bb304ceecc08.jpg :alt: Streamlink Twitch GUI :Description: A multi platform Twitch.tv browser for Streamlink :Type: Graphical User Interface :OS: :fab:`windows;fa-xl` :fab:`apple;fa-xl` :fab:`linux;fa-xl` :Author: `Sebastian Meyer `_ :Website: https://streamlink.github.io/streamlink-twitch-gui :Source code: https://github.com/streamlink/streamlink-twitch-gui :Info: An NW.js based desktop web application. Browse Twitch.tv and watch multiple streams at once. Filter streams by language, receive desktop notifications when followed channels start streaming and access the Twitch chat by using customizable chat applications. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/changelog.md0000644000175100001660000036222515003227510016503 0ustar00runnerdocker# Changelog ## streamlink 7.3.0 (2025-04-26) - Changed: download progress to use the console output stream rather than always `stderr`, which previously caused log and progress messages to be interweaved ([#6497](https://github.com/streamlink/streamlink/pull/6497), [#6496](https://github.com/streamlink/streamlink/pull/6496)) - Changed: download progress output to be a status message line at the bottom of the console output, unless `--progress=force` is set in non-interactive or unsupported environments, in which case less frequent regular progress messages are written ([#6497](https://github.com/streamlink/streamlink/pull/6497), [#6496](https://github.com/streamlink/streamlink/pull/6496)) - Fixed: potential division by zero error when formatting progress output ([#6498](https://github.com/streamlink/streamlink/pull/6498)) - Build: bumped `setuptools` requirement from `>=65.6` to `>=77` and switched to PEP 639 project license metadata format ([#6502](https://github.com/streamlink/streamlink/pull/6502)) [Full changelog](https://github.com/streamlink/streamlink/compare/7.2.0...7.3.0) ## streamlink 7.2.0 (2025-04-04) - Added: `decompress` extras marker to Streamlink's optional dependencies for installing `brotli` and `zstandard`, which were previously implied optional dependencies via the transitive dependency `urllib3` ([#6451](https://github.com/streamlink/streamlink/pull/6451)) - Changed: `--retry-streams` to allow a value of `0`, so it can be unset again if set previously ([#6455](https://github.com/streamlink/streamlink/pull/6455)) - Changed: `--quiet` to suppress all text output including errors, not just log output ([#6461](https://github.com/streamlink/streamlink/pull/6461)) - Fixed: `--logfile` affecting the console output stream ([#6461](https://github.com/streamlink/streamlink/pull/6461)) - Fixed: broken user input prompt behavior on missing or non-interactive I/O streams ([#6461](https://github.com/streamlink/streamlink/pull/6461)) - Fixed: `Plugin` classes not requiring a matching matcher ([#6466](https://github.com/streamlink/streamlink/pull/6466)) - Fixed: `--hls-audio-select` not being case-insensitive and only comparing input values rather than resolved language codes ([#6469](https://github.com/streamlink/streamlink/pull/6469), [#6479](https://github.com/streamlink/streamlink/pull/6479)) - Updated: Chrome Devtool Protocol interfaces, to match recent Chromium versions ([#6481](https://github.com/streamlink/streamlink/pull/6481)) - Updated plugins: - euronews: rewritten and fixed plugin ([#6452](https://github.com/streamlink/streamlink/pull/6452)) - kick: refactored plugin, fixed clip matcher and 403 API responses ([#6491](https://github.com/streamlink/streamlink/pull/6491)) - nicolive: fixed plugin not loading cookies from WebSocket messages ([#6441](https://github.com/streamlink/streamlink/pull/6441)) - nicolive: made the plugin always filter out blank HLS segments ([#6476](https://github.com/streamlink/streamlink/pull/6476)) - nowtvtr: removed plugin ([#6488](https://github.com/streamlink/streamlink/pull/6488)) - okru: canonicalize mobile URLs ([#6444](https://github.com/streamlink/streamlink/pull/6444)) - tf1: fixed stream matcher ([#6439](https://github.com/streamlink/streamlink/pull/6439)) [Full changelog](https://github.com/streamlink/streamlink/compare/7.1.3...7.2.0) ## streamlink 7.1.3 (2025-02-14) - Fixed: `validate.contains()` to allow all kinds of `Container` object inputs ([#6421](https://github.com/streamlink/streamlink/pull/6421)) - Updated plugins: - ceskatelevize: rewritten and fixed plugin ([#6397](https://github.com/streamlink/streamlink/pull/6397)) - nos: fixed validation schema, updated matcher ([#6420](https://github.com/streamlink/streamlink/pull/6420)) - pandalive: fixed user\_id retieval, updated matcher ([#6433](https://github.com/streamlink/streamlink/pull/6433)) - pluzz: fixed video ID schemas ([#6428](https://github.com/streamlink/streamlink/pull/6428)) - streamable: removed plugin ([#6435](https://github.com/streamlink/streamlink/pull/6435)) - tv4play: removed plugin ([#6399](https://github.com/streamlink/streamlink/pull/6399)) - twitch: refactored TwitchAPI class and access token retrieval ([#6403](https://github.com/streamlink/streamlink/pull/6403)) - wwenetwork: rewritten and fixed plugin ([#6404](https://github.com/streamlink/streamlink/pull/6404)) - Docs: replaced Windows and Linux AppImage nightly builds with preview builds triggered on each commit to `master` ([#6425](https://github.com/streamlink/streamlink/pull/6425)) - Docs: added optional/secondary Linux AppImage builds with FFmpeg being bundled ([#6415](https://github.com/streamlink/streamlink/pull/6415)) - Tests: bumped `freezegun` requirement to `>=1.5.0` ([#6406](https://github.com/streamlink/streamlink/pull/6406)) [Full changelog](https://github.com/streamlink/streamlink/compare/7.1.2...7.1.3) ## streamlink 7.1.2 (2025-01-08) - Updated plugins: - various: fixed access of URL matcher regex capture groups, affecting abematv, ard\_mediathek, bbiplayer, dailymotion, picarto, streann ([#6364](https://github.com/streamlink/streamlink/pull/6364), [#6368](https://github.com/streamlink/streamlink/pull/6368)) - chzzk: added support for clips ([#6389](https://github.com/streamlink/streamlink/pull/6389)) - dailymotion: added support for lequipe.fr ([#6372](https://github.com/streamlink/streamlink/pull/6372)) - kick: fixed 403 HTTP errors, fixed VOD URL matcher ([#6384](https://github.com/streamlink/streamlink/pull/6384)) - nicolive: fixed authentication ([#6378](https://github.com/streamlink/streamlink/pull/6378)) - tiktok: rewritten plugin, fixed live streams, added VODs ([#6381](https://github.com/streamlink/streamlink/pull/6381)) [Full changelog](https://github.com/streamlink/streamlink/compare/7.1.1...7.1.2) ## streamlink 7.1.1 (2024-12-28) - Fixed: `--show-matchers=pluginname` not working when plugins are loaded lazily ([#6361](https://github.com/streamlink/streamlink/pull/6361)) [Full changelog](https://github.com/streamlink/streamlink/compare/7.1.0...7.1.1) ## streamlink 7.1.0 (2024-12-28) - Added: `--show-matchers=pluginname` CLI argument ([#6287](https://github.com/streamlink/streamlink/pull/6287)) - Updated: `Streamlink` and `Plugin` constructors to allow both `Mapping` and `Options` as `options` types ([#6311](https://github.com/streamlink/streamlink/pull/6311)) - Fixed: uncaught DASH errors if FFmpeg is unavailable ([#6306](https://github.com/streamlink/streamlink/pull/6306)) As a side effect, if FFmpeg is unavailable, DASH streams will only return one sub-stream of the video and/or audio streams that would be muxed otherwise. - Fixed: incorrect DASH segment duration in timeline manifests ([#6323](https://github.com/streamlink/streamlink/pull/6323)) - Fixed: dynamic DASH streams incorrectly requiring the `publishTime` and `availabilityStartTime` attributes ([#6324](https://github.com/streamlink/streamlink/pull/6324)) - Fixed: incorrect DASH segment and manifest base-URL joining ([#6328](https://github.com/streamlink/streamlink/pull/6328), [#6338](https://github.com/streamlink/streamlink/pull/6338)) - Fixed: `matchers` and `arguments` objects being shared in inherited `Plugin` classes ([#6297](https://github.com/streamlink/streamlink/pull/6297), [#6298](https://github.com/streamlink/streamlink/pull/6298)) - Updated plugins: - various: replaced verbose URL matcher regexes of most plugins with multiple simple ones ([#6285](https://github.com/streamlink/streamlink/pull/6285)) - bilibili: updated schema to include MPEG-TS HLS streams ([#6332](https://github.com/streamlink/streamlink/pull/6332)) - bilibili: added back high-res `HTTPStream` streams from the v1 API with higher priority ([#5782](https://github.com/streamlink/streamlink/pull/5782)) - mangomolo: replaced media.gov.kw with 51.com.kw ([#6353](https://github.com/streamlink/streamlink/pull/6353)) - soop: rewritten authentication ([#6321](https://github.com/streamlink/streamlink/pull/6321)) - vkplay: renamed to vkvideo and updated matcher ([#6319](https://github.com/streamlink/streamlink/pull/6319)) - welt: fixed schema ([#6301](https://github.com/streamlink/streamlink/pull/6301)) - Build: removed `typing-extensions` from runtime dependencies ([#6314](https://github.com/streamlink/streamlink/pull/6314)) [Full changelog](https://github.com/streamlink/streamlink/compare/7.0.0...7.1.0) ## streamlink 7.0.0 (2024-11-04) - BREAKING: dropped support for [EOL Python 3.8](https://peps.python.org/pep-0569/#lifespan) (Win 7/8 are now unsupported) ([#6230](https://github.com/streamlink/streamlink/pull/6230)) - BREAKING/CLI: [removed deprecated config file and plugin config file paths](https://streamlink.github.io/migrations.html#config-file-paths) ([#6149](https://github.com/streamlink/streamlink/pull/6149)) - BREAKING/CLI: [removed deprecated plugin sideloading paths](https://streamlink.github.io/migrations.html#custom-plugins-sideloading-paths) ([#6150](https://github.com/streamlink/streamlink/pull/6150)) - BREAKING/CLI: [removed deprecated `--force-progress` CLI argument](https://streamlink.github.io/migrations.html#force-progress) ([#6196](https://github.com/streamlink/streamlink/pull/6196)) - BREAKING/CLI: [removed deprecated stream-type related CLI arguments](https://streamlink.github.io/migrations.html#stream-type-related-cli-arguments) ([#6232](https://github.com/streamlink/streamlink/pull/6232)) - `--hls-segment-attempts` - `--hls-segment-threads` - `--hls-segment-timeout` - `--hls-timeout` - `--http-stream-timeout` - BREAKING/API: [removed deprecated stream-type related session options](https://streamlink.github.io/migrations.html#stream-type-related-cli-arguments) ([#6232](https://github.com/streamlink/streamlink/pull/6232)) - `hls-segment-attempts` - `hls-segment-threads` - `hls-segment-timeout` - `hls-timeout` - `dash-segment-attempts` - `dash-segment-threads` - `dash-segment-timeout` - `dash-timeout` - `http-stream-timeout` - BREAKING/API: [removed deprecated import paths for `HTTPSession` and `HTTPAdapter`s](https://streamlink.github.io/migrations.html#httpsession-and-httpadapters) ([#6274](https://github.com/streamlink/streamlink/pull/6274)) - BREAKING/API: [removed deprecated import paths for `NoPluginError`, `NoStreamsError`, `PluginError` and `Plugin`](https://streamlink.github.io/migrations.html#streamlink-plugins-re-exports) ([#6274](https://github.com/streamlink/streamlink/pull/6274)) - BREAKING/packaging: dropped "32 bit" Windows x86 and Linux AppImage i686 builds ([#6052](https://github.com/streamlink/streamlink/pull/6052)) - Removed: flawed implementation of VLC-specific player variables ([#6251](https://github.com/streamlink/streamlink/pull/6251), [#6253](https://github.com/streamlink/streamlink/pull/6253)) - Deprecated: [`--verbose-player` CLI argument in favor of `--player-verbose`](https://streamlink.github.io/deprecations.html#verbose-player) ([#6227](https://github.com/streamlink/streamlink/pull/6227)) - Deprecated: [`--fifo` CLI argument in favor of `--player-fifo`](https://streamlink.github.io/deprecations.html#fifo) ([#6227](https://github.com/streamlink/streamlink/pull/6227)) - Added: warning messages for deprecated/suppressed plugin arguments ([#6240](https://github.com/streamlink/streamlink/pull/6240), [#6249](https://github.com/streamlink/streamlink/pull/6249)) - Fixed: errors on missing `stdin` file descriptor ([#6239](https://github.com/streamlink/streamlink/pull/6239)) - Fixed: `--interface` not having an effect on custom `HTTPAdapter`s ([#6223](https://github.com/streamlink/streamlink/pull/6223)) - Updated plugins: - afreeca: renamed to soop, overhauled plugin and deprecated old plugin CLI arguments ([#6247](https://github.com/streamlink/streamlink/pull/6247), [#6257](https://github.com/streamlink/streamlink/pull/6257)) - ruv: rewritten and fixed plugin ([#6262](https://github.com/streamlink/streamlink/pull/6262)) - tv3cat: updated plugin matchers ([#6242](https://github.com/streamlink/streamlink/pull/6242)) - Docs: updated documentation of various CLI arguments ([#6226](https://github.com/streamlink/streamlink/pull/6226), [#6255](https://github.com/streamlink/streamlink/pull/6255), [#6225](https://github.com/streamlink/streamlink/pull/6225)) - Chore: updated typing annotations (PEP 563, PEP 585, PEP 604, PEP 613) ([#6218](https://github.com/streamlink/streamlink/pull/6218)) - Chore: reformatted the whole code base using ruff ([#6260](https://github.com/streamlink/streamlink/pull/6260)) - Build: bumped `trio` dependency to `>=0.25.0,<1` on Python >= 3.13 ([#6244](https://github.com/streamlink/streamlink/pull/6244)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.11.0...7.0.0) ## streamlink 6.11.0 (2024-10-01) - Deprecated: [`--record-and-pipe=...` in favor of `--stdout --record=...`](https://streamlink.github.io/deprecations.html#r-record-and-pipe) (and explicitly disallowed `--stdout --output=...`) ([#6194](https://github.com/streamlink/streamlink/pull/6194)) - Fixed: error when setting both `--http-no-ssl-verify` and `--http-disable-dh` ([#6205](https://github.com/streamlink/streamlink/pull/6205)) - Fixed: `--player-passthrough` without a resolved default `--player` ([#6207](https://github.com/streamlink/streamlink/pull/6207)) - Fixed: error when stdout/stderr file descriptors are missing ([#6197](https://github.com/streamlink/streamlink/pull/6197)) - Updated: webbrowser API's Chrome devtools protocol to latest version ([#6211](https://github.com/streamlink/streamlink/pull/6211)) - Updated plugins: - crunchyroll: removed plugin ([#6179](https://github.com/streamlink/streamlink/pull/6179)) - dlive: fixed missing stream URL signature ([#6171](https://github.com/streamlink/streamlink/pull/6171)) - facebook: removed plugin ([#6199](https://github.com/streamlink/streamlink/pull/6199)) - mildom: removed plugin ([#6198](https://github.com/streamlink/streamlink/pull/6198)) - tvrby: removed plugin ([#6202](https://github.com/streamlink/streamlink/pull/6202)) - tvrplus: removed plugin ([#6203](https://github.com/streamlink/streamlink/pull/6203)) - twitch: fixed client-integrity token acquirement ([#6211](https://github.com/streamlink/streamlink/pull/6211)) - vk: fixed API params and validation schema ([#6178](https://github.com/streamlink/streamlink/pull/6178)) - webtv: removed plugin ([#6172](https://github.com/streamlink/streamlink/pull/6172)) - zengatv: removed plugin ([#6174](https://github.com/streamlink/streamlink/pull/6174)) - zhanqi: removed plugin ([#6173](https://github.com/streamlink/streamlink/pull/6173)) - Tests: removed `pytest-asyncio` dependency ([#6208](https://github.com/streamlink/streamlink/pull/6208)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.10.0...6.11.0) ## streamlink 6.10.0 (2024-09-06) - Added: official support for Python 3.13 ([#6133](https://github.com/streamlink/streamlink/pull/6133)) - Added: `--logformat` and `--logdateformat` ([#6138](https://github.com/streamlink/streamlink/pull/6138), [#6144](https://github.com/streamlink/streamlink/pull/6144)) - Added: `--ffmpeg-loglevel` ([#6161](https://github.com/streamlink/streamlink/pull/6161)) - Fixed: continuous logging errors when stdout stream was closed on the reading end ([#6142](https://github.com/streamlink/streamlink/pull/6142)) - Fixed: HTTP proxy config being used when getting webbrowser API's websocket IPC address ([#6130](https://github.com/streamlink/streamlink/pull/6130)) - Updated plugins: - booyah: removed plugin ([#6162](https://github.com/streamlink/streamlink/pull/6162)) - douyin: fixed validation schema ([#6140](https://github.com/streamlink/streamlink/pull/6140)) - galatasaraytv: removed plugin ([#6163](https://github.com/streamlink/streamlink/pull/6163)) - idf1: removed plugin ([#6164](https://github.com/streamlink/streamlink/pull/6164)) - linelive: removed plugin ([#6165](https://github.com/streamlink/streamlink/pull/6165)) - pandalive: fixed missing HTTP headers ([#6148](https://github.com/streamlink/streamlink/pull/6148)) - tiktok: fixed schema of inaccessible streams ([#6160](https://github.com/streamlink/streamlink/pull/6160)) - youtube: disabled VODs, as they are currently non-functional ([#6135](https://github.com/streamlink/streamlink/pull/6135)) - Docs: bumped `sphinx-design >=0.5.0,<=0.6.1`, enabling `sphinx >=8.0.0` ([#6137](https://github.com/streamlink/streamlink/pull/6137)) - Tests: fixed `pytest-asyncio` warnings ([#6143](https://github.com/streamlink/streamlink/pull/6143)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.9.0...6.10.0) ## streamlink 6.9.0 (2024-08-12) - Added: `streamlink.plugin.api.webbrowser` subpackage with the `aws_waf` module ([#6102](https://github.com/streamlink/streamlink/pull/6102), [#6118](https://github.com/streamlink/streamlink/pull/6118)) - Added: `max_buffer_size` kwarg to `CDPClient.session()` ([#6101](https://github.com/streamlink/streamlink/pull/6101)) - Added: webbrowser `User-Agent` header override in headless mode ([#6114](https://github.com/streamlink/streamlink/pull/6114)) - Changed: default value of `--webbrowser-headless` from `True` to `False` ([#6116](https://github.com/streamlink/streamlink/pull/6116)) - Changed: unicode characters to be unescaped in JSON output, if possible ([#6080](https://github.com/streamlink/streamlink/pull/6080)) - Fixed: potential character encoding issues in Streamlink's logger ([#6081](https://github.com/streamlink/streamlink/pull/6081)) - Updated plugins: - nicolive: updated plugin matcher ([#6127](https://github.com/streamlink/streamlink/pull/6127)) - okru: fixed validation schema ([#6085](https://github.com/streamlink/streamlink/pull/6085)) - radionet: removed plugin ([#6091](https://github.com/streamlink/streamlink/pull/6091)) - sportschau: fixed plugin (HLS streams with packed audio streams remain unsupported) ([#6104](https://github.com/streamlink/streamlink/pull/6104)) - tiktok: fixed room ID validation schema ([#6106](https://github.com/streamlink/streamlink/pull/6106)) - tvp: added support for sport.tvp.pl ([#6097](https://github.com/streamlink/streamlink/pull/6097)) - twitch: added `--twitch-force-client-integrity` ([#6113](https://github.com/streamlink/streamlink/pull/6113)) - twitch: fixed broken client-integrity token decoding+parsing ([#6113](https://github.com/streamlink/streamlink/pull/6113)) - twitch: removed the `headless=False` override ([#6117](https://github.com/streamlink/streamlink/pull/6117)) - vimeo: removed error messages for unsupported DASH streams ([#6128](https://github.com/streamlink/streamlink/pull/6128)) - vk: fixed validation schema ([#6096](https://github.com/streamlink/streamlink/pull/6096)) - vtvgo: resolved AWS Web Application Firewall bot detection ([#6102](https://github.com/streamlink/streamlink/pull/6102)) - yupptv: fixed plugin, added ad filtering ([#6093](https://github.com/streamlink/streamlink/pull/6093)) - Docs: added webbrowser API metadata to plugin descriptions ([#6115](https://github.com/streamlink/streamlink/pull/6115)) - Docs: updated build-dependencies and the furo theme ([#6126](https://github.com/streamlink/streamlink/pull/6126)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.8.3...6.9.0) ## streamlink 6.8.3 (2024-07-11) Patch release: - Updated plugins: - tiktok: new plugin ([#6073](https://github.com/streamlink/streamlink/pull/6073)) - twitch: fixed channel names with uppercase characters ([#6071](https://github.com/streamlink/streamlink/pull/6071)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.8.2...6.8.3) ## streamlink 6.8.2 (2024-07-04) Patch release: - Updated plugins: - douyin: new plugin ([#6059](https://github.com/streamlink/streamlink/pull/6059)) - huya: fixed stream URLs ([#6058](https://github.com/streamlink/streamlink/pull/6058)) - pluzz: fixed API URL, stream tokens and validation schemas ([#6048](https://github.com/streamlink/streamlink/pull/6048)) - twitch: added info log messages about ad break durations ([#6051](https://github.com/streamlink/streamlink/pull/6051)) - twitch: fixed clip URLs ([#6045](https://github.com/streamlink/streamlink/pull/6045)) - twitch: fixed discontinuity warning spam in certain circumstances ([#6022](https://github.com/streamlink/streamlink/pull/6022)) - vidio: fixed stream tokens, added metadata ([#6057](https://github.com/streamlink/streamlink/pull/6057)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.8.1...6.8.2) ## streamlink 6.8.1 (2024-06-18) Patch release: - Fixed: failed HTTPAdapter tests on some OpenSSL configurations ([#6040](https://github.com/streamlink/streamlink/pull/6040), [#6042](https://github.com/streamlink/streamlink/pull/6042)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.8.0...6.8.1) ## streamlink 6.8.0 (2024-06-17) Release highlights: - Added: sha256 checksum to log message when side-loading plugins ([#6023](https://github.com/streamlink/streamlink/pull/6023)) - Added: `SSLContextAdapter` to `streamlink.session.http` ([#6024](https://github.com/streamlink/streamlink/pull/6024)) - Deprecated: [old re-exports in `streamlink.plugins` package](https://streamlink.github.io/deprecations.html#streamlink-plugins-re-exports) ([#6005](https://github.com/streamlink/streamlink/pull/6005)) - Updated plugins: - bilibili: fixed validation schema for offline channels ([#6032](https://github.com/streamlink/streamlink/pull/6032)) - chzzk: fixed channels without content ([#6002](https://github.com/streamlink/streamlink/pull/6002)) - cnbce: new plugin ([#6029](https://github.com/streamlink/streamlink/pull/6029)) - kick: new plugin ([#6012](https://github.com/streamlink/streamlink/pull/6012), [#6021](https://github.com/streamlink/streamlink/pull/6021), [#6024](https://github.com/streamlink/streamlink/pull/6024)) - tf1: added authentication via `--tf1-email` and `--tf1-password` ([#5983](https://github.com/streamlink/streamlink/pull/5983)) - tvp: fixed live streams ([#6037](https://github.com/streamlink/streamlink/pull/6037)) - welt: fixed live streams ([#6011](https://github.com/streamlink/streamlink/pull/6011)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.7.4...6.8.0) ## streamlink 6.7.4 (2024-05-12) Patch release: - Refactored: CLI errors ([#5958](https://github.com/streamlink/streamlink/pull/5958)) - Updated plugins: - afreeca: updated stream qualities ([#5953](https://github.com/streamlink/streamlink/pull/5953)) - afreeca: added `--afreeca-stream-password` ([#5952](https://github.com/streamlink/streamlink/pull/5952)) - chzzk: new plugin ([#5731](https://github.com/streamlink/streamlink/pull/5731)) - nownews: removed plugin ([#5961](https://github.com/streamlink/streamlink/pull/5961)) - turkuvaz: fixed HLS streams ([#5946](https://github.com/streamlink/streamlink/pull/5946)) - Docs: clarified plugin request rules ([#5949](https://github.com/streamlink/streamlink/pull/5949)) - Build: fixed build issues on Windows ([#5990](https://github.com/streamlink/streamlink/pull/5990)) - Build: removed `exceptiongroup` dependency on Python >= 3.11 ([#5987](https://github.com/streamlink/streamlink/pull/5987)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.7.3...6.7.4) ## streamlink 6.7.3 (2024-04-14) Patch release: - Fixed: file output paths being able to exceed max file/directory name length ([#5921](https://github.com/streamlink/streamlink/pull/5921), [#5925](https://github.com/streamlink/streamlink/pull/5925)) - Fixed: propagation of `KeyboardInterrupt`/`SystemExit` in `streamlink.webbrowser` ([#5930](https://github.com/streamlink/streamlink/pull/5930)) - Fixed: compatibility with `exceptiongroup<=1.1.1` ([#5930](https://github.com/streamlink/streamlink/pull/5930)) - Fixed: `plugin.api.validate.parse_qsd` input type validation ([#5932](https://github.com/streamlink/streamlink/pull/5932)) - Updated plugins: - mangomolo: fixed missing referer header and updated URL matcher ([#5923](https://github.com/streamlink/streamlink/pull/5923), [#5926](https://github.com/streamlink/streamlink/pull/5926)) - pluto: rewritten plugin ([#5910](https://github.com/streamlink/streamlink/pull/5910)) - showroom: fixed geo-block check preventing stream access ([#5911](https://github.com/streamlink/streamlink/pull/5911)) - vkplay: updated URL matcher ([#5908](https://github.com/streamlink/streamlink/pull/5908)) - Tests: fixed test failure when running tests from the `bdist` build directory ([#5933](https://github.com/streamlink/streamlink/pull/5933)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.7.2...6.7.3) ## streamlink 6.7.2 (2024-03-23) Patch release: - Build: reverted `trio` version requirement bump ([#5902](https://github.com/streamlink/streamlink/pull/5902)) - Build: fixed incorrect `pytest` version requirement ([#5901](https://github.com/streamlink/streamlink/pull/5901)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.7.1...6.7.2) ## streamlink 6.7.1 (2024-03-19) Patch release: - Fixed: CLI download progress missing the last data chunk ([#5887](https://github.com/streamlink/streamlink/pull/5887)) - Fixed: compatibility with `trio>=0.25` ([#5895](https://github.com/streamlink/streamlink/pull/5895)) - Updated plugins: - tv3cat: fixed plugin and added VODs ([#5890](https://github.com/streamlink/streamlink/pull/5890)) - vimeo: fixed event streams and embedded player URLs ([#5892](https://github.com/streamlink/streamlink/pull/5892), [#5899](https://github.com/streamlink/streamlink/pull/5899)) - Build: bumped `trio` dependency version requirement to `>=0.25,<1` ([#5895](https://github.com/streamlink/streamlink/pull/5895)) - Build: added `exceptiongroup` dependency ([#5895](https://github.com/streamlink/streamlink/pull/5895)) - Tests: fixed root logger level not being reset ([#5888](https://github.com/streamlink/streamlink/pull/5888), [#5897](https://github.com/streamlink/streamlink/pull/5897)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.7.0...6.7.1) ## streamlink 6.7.0 (2024-03-09) Release highlights: - Added: repeatable `--plugin-dir` CLI argument ([#5866](https://github.com/streamlink/streamlink/pull/5866)) - Deprecated: `--plugin-dirs` CLI argument with comma separated paths ([#5866](https://github.com/streamlink/streamlink/pull/5866)) - Fixed: independent encryption status of HLS initialization sections ([#5861](https://github.com/streamlink/streamlink/pull/5861)) - Fixed: objects of default session options being shared between sessions ([#5875](https://github.com/streamlink/streamlink/pull/5875)) - Updated plugins: - bloomberg: fixed data regex ([#5869](https://github.com/streamlink/streamlink/pull/5869)) - ltv_lsm_lv: fixed player ID retrieval, removed custom HLS implementation ([#5858](https://github.com/streamlink/streamlink/pull/5858)) - mangomolo: new plugin ([#5852](https://github.com/streamlink/streamlink/pull/5852)) - ustvnow: updated matcher ([#5881](https://github.com/streamlink/streamlink/pull/5881)) - vimeo: fixed optional DASH streams of live events ([#5854](https://github.com/streamlink/streamlink/pull/5854)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.6.2...6.7.0) ## streamlink 6.6.2 (2024-02-20) Patch release: - Fixed: missing plugin override log message in non-editable installs ([#5844](https://github.com/streamlink/streamlink/pull/5844)) - Fixed: incorrect `setuptools` min. version in build requirements ([#5842](https://github.com/streamlink/streamlink/pull/5842)) - Updated plugins: - afreeca: fixed broadcast number regex ([#5847](https://github.com/streamlink/streamlink/pull/5847)) - afreeca: added support for stream metadata ([#5849](https://github.com/streamlink/streamlink/pull/5849)) - hiplayer: removed media.gov.kw matcher ([#5848](https://github.com/streamlink/streamlink/pull/5848)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.6.1...6.6.2) ## streamlink 6.6.1 (2024-02-17) Patch release: - Fixed: plugin arguments in `--help` output ([#5838](https://github.com/streamlink/streamlink/pull/5838)) - Docs: removed empty plugin sections in docs and man page ([#5838](https://github.com/streamlink/streamlink/pull/5838)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.6.0...6.6.1) ## streamlink 6.6.0 (2024-02-16) Release highlights: - Implemented: lazy plugins loading ([#5793](https://github.com/streamlink/streamlink/pull/5793), [#5822](https://github.com/streamlink/streamlink/pull/5822)) Streamlink's built-in plugins will now be loaded on demand when resolving input URLs. This improves initial loading times and reduces total memory consumption. - Refactored: Streamlink session module (and related) - Moved: `streamlink.session` into a sub-package ([#5807](https://github.com/streamlink/streamlink/pull/5807)) - Moved: `streamlink.plugin.api.http_session` to `streamlink.session.http` ([#5807](https://github.com/streamlink/streamlink/pull/5807)) - Refactored: `Streamlink` class internals ([#5807](https://github.com/streamlink/streamlink/pull/5807), [#5814](https://github.com/streamlink/streamlink/pull/5814)) - Deprecated: [`Streamlink.{get,load}_plugins()` methods](https://streamlink.github.io/deprecations.html#streamlink-get-load-plugins) ([#5818](https://github.com/streamlink/streamlink/pull/5818)) - Deprecated: [direct imports of `HTTPSession` and imports from `streamlink.plugin.api.http_session`](https://streamlink.github.io/deprecations.html#httpsession-and-httpadapters) ([#5818](https://github.com/streamlink/streamlink/pull/5818)) - Refactored: `streamlink.utils.args` module ([#5778](https://github.com/streamlink/streamlink/pull/5778), [#5781](https://github.com/streamlink/streamlink/pull/5781), [#5815](https://github.com/streamlink/streamlink/pull/5815)) - Updated plugins: - aloula: fixed missing HTTP headers ([#5792](https://github.com/streamlink/streamlink/pull/5792)) - foxtr: removed plugin ([#5827](https://github.com/streamlink/streamlink/pull/5827)) - huya: fixed stream URLs ([#5785](https://github.com/streamlink/streamlink/pull/5785)) - nowtvtr: new plugin ([#5827](https://github.com/streamlink/streamlink/pull/5827)) - qq: removed plugin ([#5806](https://github.com/streamlink/streamlink/pull/5806)) - rtbf: removed plugin ([#5801](https://github.com/streamlink/streamlink/pull/5801)) - Tests: improved overall test execution time ([#5799](https://github.com/streamlink/streamlink/pull/5799), [#5805](https://github.com/streamlink/streamlink/pull/5805)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.5.1...6.6.0) ## streamlink 6.5.1 (2024-01-16) Patch release: - Fixed: file output path log message on py38/py39 ([#5728](https://github.com/streamlink/streamlink/pull/5728)) - Improved: warning message when using quoted player paths (in config files) ([#5757](https://github.com/streamlink/streamlink/pull/5757)) - Updated plugins: - artetv: updated API response validation schema ([#5774](https://github.com/streamlink/streamlink/pull/5774)) - atresplayer: updated API response validation schema ([#5742](https://github.com/streamlink/streamlink/pull/5742)) - bigo: reimplemented plugin ([#5754](https://github.com/streamlink/streamlink/pull/5754)) - bilibili: fixed stream resolving issues on channels with custom layouts ([#5771](https://github.com/streamlink/streamlink/pull/5771)) - huya: added stream CDN availability check ([#5745](https://github.com/streamlink/streamlink/pull/5745)) - twitch: disabled Chromium headless mode on client-integrity token acquirement ([#5758](https://github.com/streamlink/streamlink/pull/5758)) - vidio: fixed missing API request cookies ([#5762](https://github.com/streamlink/streamlink/pull/5762)) - zattoo: fixed audio/video sync issues ([#5739](https://github.com/streamlink/streamlink/pull/5739)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.5.0...6.5.1) ## streamlink 6.5.0 (2023-12-16) Release highlights: - Fixed: `UserWarning` being emitted by recent `pycountry` releases when parsing certain language codes ([#5722](https://github.com/streamlink/streamlink/pull/5722)) - Fixed: trace logging setup in `WebsocketClient` implementation ([#5705](https://github.com/streamlink/streamlink/pull/5705)) - Updated plugins: - btv: switched to HLS multivariant playlists ([#5698](https://github.com/streamlink/streamlink/pull/5698)) - gulli: rewritten plugin ([#5725](https://github.com/streamlink/streamlink/pull/5725)) - twitch: removed/disabled `--twitch-disable-reruns` ([#5704](https://github.com/streamlink/streamlink/pull/5704)) - twitch: enabled `check_streams` HLS option, to ensure early stream availability without querying the delayed Twitch API ([#5708](https://github.com/streamlink/streamlink/pull/5708)) - twitch: removed unnecessary Twitch API error messages for offline channels ([#5709](https://github.com/streamlink/streamlink/pull/5709)) - wasd: removed plugin ([#5711](https://github.com/streamlink/streamlink/pull/5711)) - Build: added support for `versioningit >=3.0.0`, with backward compatibility ([#5721](https://github.com/streamlink/streamlink/pull/5721)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.4.2...6.5.0) ## streamlink 6.4.2 (2023-11-28) Patch release: - Fixed: HLS segment maps being written to the output multiple times ([#5689](https://github.com/streamlink/streamlink/pull/5689)) - Fixed plugins: - bilibili: rewritten plugin ([#5693](https://github.com/streamlink/streamlink/pull/5693)) - piczel: updated HLS URLs ([#5690](https://github.com/streamlink/streamlink/pull/5690)) - ssh101: fixed stream URL retrieval ([#5686](https://github.com/streamlink/streamlink/pull/5686)) - Docs: added missing `Cache` API docs ([#5688](https://github.com/streamlink/streamlink/pull/5688)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.4.1...6.4.2) ## streamlink 6.4.1 (2023-11-22) Patch release: - Fixed: libxml2 2.12.0 compatibility ([#5682](https://github.com/streamlink/streamlink/pull/5682)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.4.0...6.4.1) ## streamlink 6.4.0 (2023-11-21) Release highlights: - Added: missing support for dynamic DASH manifests with `SegmentList`s ([#5654](https://github.com/streamlink/streamlink/pull/5654), [#5657](https://github.com/streamlink/streamlink/pull/5657)) - Added: warning log message when skipping DASH segments between manifest reloads ([#5659](https://github.com/streamlink/streamlink/pull/5659)) - Added plugins: nasaplus ([#5664](https://github.com/streamlink/streamlink/pull/5664)) - Updated plugins: - raiplay: added VOD support with authentication `--raiplay-email` / `--raiplay-password` / `--raiplay-purge-credentials` ([#5662](https://github.com/streamlink/streamlink/pull/5662)) - telemadrid: fixed XPath query ([#5653](https://github.com/streamlink/streamlink/pull/5653)) - tvp: fixed tvp.info ([#5645](https://github.com/streamlink/streamlink/pull/5645)) - youtube: fixed video ID retrieval ([#5673](https://github.com/streamlink/streamlink/pull/5673)) - Docs: added validation schema API docs and API guide ([#5652](https://github.com/streamlink/streamlink/pull/5652), [#5655](https://github.com/streamlink/streamlink/pull/5655)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.3.1...6.4.0) ## streamlink 6.3.1 (2023-10-26) Patch release: - Fixed plugins: - welt: rewritten plugin ([#5637](https://github.com/streamlink/streamlink/pull/5637)) - Build: fixed tests when running from sdist ([#5636](https://github.com/streamlink/streamlink/pull/5636)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.3.0...6.3.1) ## streamlink 6.3.0 (2023-10-25) Release highlights: - Added: warning log message when skipping HLS segments between playlist reloads ([#5607](https://github.com/streamlink/streamlink/pull/5607)) - Refactored: internals of segmented stream implementations (base classes, HLS, DASH) - Added: base `Segment` dataclass and made segmented streams inherit from it ([#5594](https://github.com/streamlink/streamlink/pull/5594)) - Moved: modules into sub-packages (import paths of public APIs remain the same) ([#5593](https://github.com/streamlink/streamlink/pull/5593)) - Renamed: various non-public HLS class methods/attributes and functions ([#5526](https://github.com/streamlink/streamlink/pull/5526)) - Removed: `Sequence` segment wrapper from HLS implementation ([#5526](https://github.com/streamlink/streamlink/pull/5526)) - Fixed: DASH manifest not respecting the `minBufferTime` ([#5610](https://github.com/streamlink/streamlink/pull/5610)) - Fixed: URL matchers of HLS/DASH protocol plugins ([#5616](https://github.com/streamlink/streamlink/pull/5616), [#5617](https://github.com/streamlink/streamlink/pull/5617)) - Fixed: bandwidth parsing issue in HLS multivariant playlists ([#5619](https://github.com/streamlink/streamlink/pull/5619)) - Fixed plugins: - dlive: fixed live streams and fixed VODs ([#5622](https://github.com/streamlink/streamlink/pull/5622), [#5623](https://github.com/streamlink/streamlink/pull/5623)) - goodgame: rewritten plugin using goodgame API v4 ([#5586](https://github.com/streamlink/streamlink/pull/5586), [#5597](https://github.com/streamlink/streamlink/pull/5597)) - mitele: updated gbx API calls from v2 to v3 ([#5624](https://github.com/streamlink/streamlink/pull/5624)) - twitch: fixed error handling of geo-restricted or inaccessible streams ([#5591](https://github.com/streamlink/streamlink/pull/5591)) - Removed plugins: - ntv: static stream URLs ([#5604](https://github.com/streamlink/streamlink/pull/5604)) - vlive: offline ([#5599](https://github.com/streamlink/streamlink/pull/5599)) - Build: dropped `versioningit` build-requirement when building from sdist tarball (version string has always been built-in while `versioningit` performed a no-op) ([#5632](https://github.com/streamlink/streamlink/pull/5632)) - Packaging: added missing shell completions build-script to sdist ([#5625](https://github.com/streamlink/streamlink/pull/5625)) - Docs: clarified section about building from source (sdist/git vs. GitHub tarballs) ([#5633](https://github.com/streamlink/streamlink/pull/5633)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.2.1...6.3.0) ## streamlink 6.2.1 (2023-10-03) Patch release: - Added: official support for Python 3.12 ([#5576](https://github.com/streamlink/streamlink/pull/5576)) - Fixed plugins: goodgame ([#5557](https://github.com/streamlink/streamlink/pull/5557)), nos ([#5565](https://github.com/streamlink/streamlink/pull/5565)), pandalive ([#5569](https://github.com/streamlink/streamlink/pull/5569)), wwenetwork ([#5559](https://github.com/streamlink/streamlink/pull/5559)) - Build: added custom setuptools build-backend override which fixes issues with building Windows-specific wheels ([#5558](https://github.com/streamlink/streamlink/pull/5558)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.2.0...6.2.1) ## streamlink 6.2.0 (2023-09-14) Release highlights: - Added: `--player-env` CLI argument ([#5535](https://github.com/streamlink/streamlink/pull/5535)) - Added: OpenSSL version to debug log output ([#5506](https://github.com/streamlink/streamlink/pull/5506)) - Updated: segmented stream internals and typing ([#5504](https://github.com/streamlink/streamlink/pull/5504), [#5507](https://github.com/streamlink/streamlink/pull/5507)) - Updated: internal HLS tag parsing setup and parser state ([#5513](https://github.com/streamlink/streamlink/pull/5513), [#5521](https://github.com/streamlink/streamlink/pull/5521)) - Fixed: HLS streams not ending on playlist reload with endlist tag and no new segments ([#5538](https://github.com/streamlink/streamlink/pull/5538)) - Fixed: missing file encoding when writing a log file ([#5532](https://github.com/streamlink/streamlink/pull/5532)) - Added plugins: piaulizaportal ([#5508](https://github.com/streamlink/streamlink/pull/5508)) - Fixed plugins: hiplayer ([#5534](https://github.com/streamlink/streamlink/pull/5534)), nicolive ([#5529](https://github.com/streamlink/streamlink/pull/5529)), pluto ([#5551](https://github.com/streamlink/streamlink/pull/5551)) - Docs: added list of supported metadata variables for each plugin ([#5517](https://github.com/streamlink/streamlink/pull/5517), [#5519](https://github.com/streamlink/streamlink/pull/5519)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.1.0...6.2.0) ## streamlink 6.1.0 (2023-08-16) Release highlights: - Added: `--hls-segment-queue-threshold` for being able to configure when to stop HLS streams early on missing segments ([#5478](https://github.com/streamlink/streamlink/pull/5478)) - Fixed: config file parsing issues and made parsing argument values more strict ([#5484](https://github.com/streamlink/streamlink/pull/5484)) - Fixed: race condition when reading and validating the FFmpeg version string ([#5480](https://github.com/streamlink/streamlink/pull/5480)) - Fixed plugins: atresplayer ([#5477](https://github.com/streamlink/streamlink/pull/5477)) - Docs: added code examples for the [removal of `Streamlink.{g,s}et_plugin_option`](https://streamlink.github.io/migrations.html#streamlink-g-s-et-plugin-option) ([#5497](https://github.com/streamlink/streamlink/pull/5497)) - Build: fixed entry-points config issues with setuptools `68.1.0` ([#5500](https://github.com/streamlink/streamlink/pull/5500)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.0.1...6.1.0) ## streamlink 6.0.1 (2023-08-02) Patch release: - Added: missing `options` argument to `Streamlink.streams()` ([#5469](https://github.com/streamlink/streamlink/pull/5469)) - Fixed: migration docs and the [`6.0.0` changelog](https://streamlink.github.io/changelog.html#streamlink-6-0-0-2023-07-20) of the [`Streamlink.{g,s}et_plugin_option()` removal](https://streamlink.github.io/migrations.html#streamlink-g-s-et-plugin-option) ([#5471](https://github.com/streamlink/streamlink/pull/5471)) - Fixed plugins: huya ([#5467](https://github.com/streamlink/streamlink/pull/5467)) - Docs: updated build-dependencies and the furo theme ([#5464](https://github.com/streamlink/streamlink/pull/5464), [#5465](https://github.com/streamlink/streamlink/pull/5465)) [Full changelog](https://github.com/streamlink/streamlink/compare/6.0.0...6.0.1) ## streamlink 6.0.0 (2023-07-20) Breaking changes: - BREAKING: dropped support for Python 3.7 ([#5302](https://github.com/streamlink/streamlink/pull/5302)) - BREAKING: [turned `--player` CLI argument into a player-path-only argument](https://streamlink.github.io/migrations.html#player-path-only-player-cli-argument) ([#5305](https://github.com/streamlink/streamlink/issues/5305), [#5310](https://github.com/streamlink/streamlink/pull/5310)) Its value won't be interpreted as a command line string anymore, so paths with whitespace don't require additional quotation. Custom player arguments now always need to be set via `--player-args`. - BREAKING: [removed deprecated `{filename}` variable from `--player-args`](https://streamlink.github.io/migrations.html#filename-variable-in-player-args) ([#5310](https://github.com/streamlink/streamlink/pull/5310)) - BREAKING/API: [removed support for the deprecated `Plugin.can_handle_url()` / `Plugin.priority()` classmethods](https://streamlink.github.io/migrations.html#plugin-can-handle-url-and-plugin-priority) ([#5403](https://github.com/streamlink/streamlink/pull/5403)) - BREAKING/API: [removed deprecated compatibility wrapper for the `Plugin` constructor](https://streamlink.github.io/migrations.html#plugin-init-self-url-compatibility-wrapper) ([#5402](https://github.com/streamlink/streamlink/pull/5402)) - BREAKING/API: [removed `Streamlink.{g,s}et_plugin_option()`](https://streamlink.github.io/migrations.html#streamlink-g-s-et-plugin-option) ([#5033](https://github.com/streamlink/streamlink/pull/5033)) - BREAKING/API: [removed deprecated global plugin arguments](https://streamlink.github.io/migrations.html#global-plugin-arguments) ([#5033](https://github.com/streamlink/streamlink/pull/5033)) - BREAKING/API: [removed deprecated `streamlink.plugin.api.validate.text`](https://streamlink.github.io/migrations.html#plugin-api-validate-text) ([#5404](https://github.com/streamlink/streamlink/pull/5404)) - BREAKING/API: [fixed/changed signatures of `HTTPStream`, `HLSStream` and `HLSStream.parse_variant_playlist()`](https://streamlink.github.io/migrations.html#httpstream-and-hlsstream-signature-changes) ([#5429](https://github.com/streamlink/streamlink/pull/5429)) - BREAKING/packaging: new signing key [`44448A298D5C3618`](https://keyserver.ubuntu.com/pks/lookup?search=44448A298D5C3618&fingerprint=on&op=index) ([#5449](https://github.com/streamlink/streamlink/pull/5449)) Release highlights: - Added: experimental `streamlink.webbrowser` API for extracting data from websites using the system's Chromium-based web browser ([#5380](https://github.com/streamlink/streamlink/issues/5380), [#5381](https://github.com/streamlink/streamlink/pull/5381), [#5386](https://github.com/streamlink/streamlink/pull/5386), [#5388](https://github.com/streamlink/streamlink/pull/5388), [#5410](https://github.com/streamlink/streamlink/pull/5410)) See the [`--webbrowser`, `--webbrowser-executable` and related CLI arguments](https://streamlink.github.io/cli.html#web-browser-options) for more - Added: client-integrity token support to Twitch plugin using the `streamlink.webbrowser` API (currently only used as a fallback when acquiring the access token fails) ([#5414](https://github.com/streamlink/streamlink/pull/5414)) - Added: `{playertitleargs}` variable to `--player-args` ([#5310](https://github.com/streamlink/streamlink/pull/5310)) - Added: `with_{video,audio}_only` parameters to `DASHStream.parse_manifest()` ([#5340](https://github.com/streamlink/streamlink/pull/5340)) - Changed: HLS streams to stop early on missing `EXT-X-ENDLIST` tag when polling the playlist doesn't yield new segments for twice its targetduration value ([#5330](https://github.com/streamlink/streamlink/pull/5330)) - Fixed: regex of optional protocol plugin parameters ([#5367](https://github.com/streamlink/streamlink/pull/5367)) - Fixed plugins: lrt ([#5444](https://github.com/streamlink/streamlink/pull/5444)), mediavitrina ([#5376](https://github.com/streamlink/streamlink/pull/5376)), mitele ([#5436](https://github.com/streamlink/streamlink/pull/5436)), NRK ([#5408](https://github.com/streamlink/streamlink/pull/5408)), pluzz ([#5369](https://github.com/streamlink/streamlink/pull/5369)), rtvs ([#5443](https://github.com/streamlink/streamlink/pull/5443)), showroom ([#5390](https://github.com/streamlink/streamlink/pull/5390)), turkuvaz ([#5374](https://github.com/streamlink/streamlink/pull/5374)), vimeo ([#5335](https://github.com/streamlink/streamlink/pull/5335)), youtube ([#5351](https://github.com/streamlink/streamlink/pull/5351)) - Docs: added migrations page for further guidance on resolving breaking changes ([#5433](https://github.com/streamlink/streamlink/pull/5433)) - Docs: split up, updated and improved API docs ([#5398](https://github.com/streamlink/streamlink/pull/5398)) - Build: moved project metadata to pyproject.toml (PEP621) ([#5438](https://github.com/streamlink/streamlink/pull/5438)) - Dependencies: added `trio` ([#5386](https://github.com/streamlink/streamlink/pull/5386)), `trio-websocket` and `typing-extensions` ([#5388](https://github.com/streamlink/streamlink/pull/5388)), and removed `importlib_metadata` ([#5302](https://github.com/streamlink/streamlink/pull/5302)) [Full changelog](https://github.com/streamlink/streamlink/compare/5.5.1...6.0.0) ## streamlink 5.5.1 (2023-05-08) Patch release: - Fixed: shifting time offset when reloading HLS playlists ([#5321](https://github.com/streamlink/streamlink/pull/5321)) - Fixed: import of `create_urllib3_context` on `urllib3 <2.0.0` ([#5333](https://github.com/streamlink/streamlink/pull/5333)) - Fixed: Vimeo plugin ([#5331](https://github.com/streamlink/streamlink/pull/5331)) [Full changelog](https://github.com/streamlink/streamlink/compare/5.5.0...5.5.1) ## streamlink 5.5.0 (2023-05-05) Release highlights: - Added: `--no-config` ([#5314](https://github.com/streamlink/streamlink/pull/5314)) - Added: `--player-external-http-interface` ([#5295](https://github.com/streamlink/streamlink/pull/5295)) - Fixed: M3U8 attribute parsing issue ([#5307](https://github.com/streamlink/streamlink/pull/5307)) - Fixed: various minor plugin issues ([#5291](https://github.com/streamlink/streamlink/pull/5291), [#5299](https://github.com/streamlink/streamlink/pull/5299), [#5306](https://github.com/streamlink/streamlink/pull/5306)) - Build: bumped urllib3 to `>=1.26.0,<3` and fixed compatibility issues with `urllib3 >=2.0.0` ([#5326](https://github.com/streamlink/streamlink/pull/5326), [#5325](https://github.com/streamlink/streamlink/pull/5325)) - Docs: bumped furo theme to `2023.03.27` ([#5301](https://github.com/streamlink/streamlink/pull/5301)) - Docs: bumped build dependencies `sphinx >=5.0.0,<7`, `myst-parser >=1.0.0,<2` and `sphinx-design >=0.4.1,<1` ([#5301](https://github.com/streamlink/streamlink/pull/5301)) [Full changelog](https://github.com/streamlink/streamlink/compare/5.4.0...5.5.0) ## streamlink 5.4.0 (2023-04-12) Release highlights: - Added: `--progress` CLI argument and [deprecated `--force-progress`](https://streamlink.github.io/deprecations.html#deprecation-of-force-progress) ([#5268](https://github.com/streamlink/streamlink/pull/5268)) - Added: `--dash-manifest-reload-attempts` and respective session option ([#5208](https://github.com/streamlink/streamlink/pull/5208)) - Improved: DASH segment availability/download logging ([#5214](https://github.com/streamlink/streamlink/pull/5214), [#5235](https://github.com/streamlink/streamlink/pull/5235)) - Refactored: DASH parser + stream implementation ([#5221](https://github.com/streamlink/streamlink/pull/5221), [#5224](https://github.com/streamlink/streamlink/pull/5224), [#5225](https://github.com/streamlink/streamlink/pull/5225), [#5244](https://github.com/streamlink/streamlink/pull/5244), [#5248](https://github.com/streamlink/streamlink/pull/5248)) - Fixed: DASH segment template numbers and availability times ([#5213](https://github.com/streamlink/streamlink/pull/5213), [#5217](https://github.com/streamlink/streamlink/pull/5217), [#5233](https://github.com/streamlink/streamlink/pull/5233)) - Fixed: DASH manifest mediaPresentationDuration and period duration ([#5226](https://github.com/streamlink/streamlink/pull/5226)) - Fixed: DASH manifest suggestedPresentationDelay ([#5215](https://github.com/streamlink/streamlink/pull/5215)) - Fixed: various DASH manifest parsing bugs ([#5247](https://github.com/streamlink/streamlink/pull/5247)) - Fixed: DASH timeline IDs not being unique ([#5199](https://github.com/streamlink/streamlink/pull/5199)) - Fixed: DASH substreams not having synced timelines ([#5262](https://github.com/streamlink/streamlink/pull/5262)) - Fixed: queued DASH segments being downloaded after closing the stream ([#5236](https://github.com/streamlink/streamlink/pull/5236), [#5237](https://github.com/streamlink/streamlink/pull/5237)) - Fixed: incorrect min/max values of certain numeric CLI arguments ([#5239](https://github.com/streamlink/streamlink/pull/5239)) - Fixed: all naive datetime objects and made them timezone-aware ([#5210](https://github.com/streamlink/streamlink/pull/5210)) - Fixed: TV5monde plugin with new implementation ([#5206](https://github.com/streamlink/streamlink/pull/5206)) - Fixed: Steam plugin missing CDN auth data in stream URLs ([#5222](https://github.com/streamlink/streamlink/pull/5222)) - Fixed: Vimeo plugin's playerConfig regex ([#5227](https://github.com/streamlink/streamlink/pull/5227)) - Fixed: VKplay plugin's validation schema ([#5251](https://github.com/streamlink/streamlink/pull/5251)) - Fixed: Twitcasting plugin with new implementation ([#5255](https://github.com/streamlink/streamlink/pull/5255)) - Tests: fixed setuptools/pkg\_resources DeprecationWarnings ([#5167](https://github.com/streamlink/streamlink/pull/5167), [#5230](https://github.com/streamlink/streamlink/pull/5230)) - Tests: fixed ResourceWarnings due to stale file handles ([#5242](https://github.com/streamlink/streamlink/pull/5242)) - Added plugins: indihometv ([#5266](https://github.com/streamlink/streamlink/pull/5266)), telemadrid ([#5212](https://github.com/streamlink/streamlink/pull/5212)) - Removed plugins: nbcnews ([#5279](https://github.com/streamlink/streamlink/pull/5279)), useetv ([#5266](https://github.com/streamlink/streamlink/pull/5266)) [Full changelog](https://github.com/streamlink/streamlink/compare/5.3.1...5.4.0) ## streamlink 5.3.1 (2023-02-25) Patch release: - Fixed: `http-trust-env` session option name (`--http-ignore-env` CLI parameter) ([#5193](https://github.com/streamlink/streamlink/pull/5193)) - Fixed: missing byterange attribute of initialization segments in DASH streams ([#5189](https://github.com/streamlink/streamlink/pull/5189)) - Fixed: broken BaseURL context in DASH streams ([#5194](https://github.com/streamlink/streamlink/pull/5194)) - Fixed: detection of certain encrypted DASH streams ([#5196](https://github.com/streamlink/streamlink/pull/5196)) [Full changelog](https://github.com/streamlink/streamlink/compare/5.3.0...5.3.1) ## streamlink 5.3.0 (2023-02-18) Release highlights: - Project meta: dropped Open Collective sponsoring platform and updated the project's README, as well as the docs' donation/support page ([#5143](https://github.com/streamlink/streamlink/pull/5143)) - Deprecated: global plugin arguments ([#5140](https://github.com/streamlink/streamlink/pull/5140)) - Fixed: muxed streams sometimes missing data at the end ([#5162](https://github.com/streamlink/streamlink/pull/5162)) - Fixed: named pipes sometimes not being cleaned up properly ([#5162](https://github.com/streamlink/streamlink/pull/5162)) - Fixed: new YouTube channel URLs not being matched ([#5137](https://github.com/streamlink/streamlink/pull/5137)) - Fixed: KeyError when accessing certain YouTube URLs ([#5139](https://github.com/streamlink/streamlink/pull/5139)) - Fixed: M3U8 attribute parsing ([#5125](https://github.com/streamlink/streamlink/pull/5125)) - Fixed: NimoTV streams stopping after a few seconds ([#5147](https://github.com/streamlink/streamlink/pull/5147)) - Fixed: delimiter of `http-query-params` session option string setter ([#5176](https://github.com/streamlink/streamlink/pull/5176)) - Fixed: sdist/bdist missing some files ([#5119](https://github.com/streamlink/streamlink/pull/5119), [#5141](https://github.com/streamlink/streamlink/pull/5141)) - Docs: fixed `Streamlink.set_option()` docstring ([#5176](https://github.com/streamlink/streamlink/pull/5176)) - Docs: improved CLI tutorial ([#5157](https://github.com/streamlink/streamlink/pull/5157)) - Docs: improved install page ([#5178](https://github.com/streamlink/streamlink/pull/5178)) - Removed plugins: funimationnow ([#5128](https://github.com/streamlink/streamlink/pull/5128)), schoolism ([#5127](https://github.com/streamlink/streamlink/pull/5127)) [Full changelog](https://github.com/streamlink/streamlink/compare/5.2.1...5.3.0) ## streamlink 5.2.1 (2023-01-23) No code changes. Please see the changelog of the [`5.2.0`](https://streamlink.github.io/changelog.html#streamlink-5-2-0-2023-01-23) release. - Reverted: PyPI deploy script changes ([#5116](https://github.com/streamlink/streamlink/pull/5116)) [Full changelog](https://github.com/streamlink/streamlink/compare/5.2.0...5.2.1) ## streamlink 5.2.0 (2023-01-23) Release highlights: - Added: new stream read/output loop, to be able to detect player process termination while stream output is paused (ad filtering, etc.) ([#5024](https://github.com/streamlink/streamlink/pull/5024)) - Added: support for named plugin matchers ([#5103](https://github.com/streamlink/streamlink/pull/5103), [#5107](https://github.com/streamlink/streamlink/pull/5107)) - Added: Python warnings capturing to streamlink logger and added `StreamlinkWarning` ([#5072](https://github.com/streamlink/streamlink/pull/5072)) - Changed: deprecation log messages to warnings, and added missing warnings for [previously deprecated things](https://streamlink.github.io/deprecations.html) ([#5072](https://github.com/streamlink/streamlink/pull/5072)) - Deprecated: usage of `validate.text` in favor of `str` ([#5090](https://github.com/streamlink/streamlink/pull/5090)) - Improved: `Streamlink` session option getters/setters ([#5076](https://github.com/streamlink/streamlink/pull/5076)) - Fixed: incorrect inheritance of `NoPluginError` and removed unneeded `url` parameter from `NoStreamsError` ([#5088](https://github.com/streamlink/streamlink/pull/5088)) - Fixed: error handling in Twitch access token acquirement ([#5011](https://github.com/streamlink/streamlink/pull/5011)) - Fixed: dogan plugin ([#5053](https://github.com/streamlink/streamlink/pull/5053)) - Fixed: ceskatelevize plugin, added sport/sportplus/decko ([#5063](https://github.com/streamlink/streamlink/pull/5063)) - Added plugins: mixcloud ([#5096](https://github.com/streamlink/streamlink/pull/5096)), vkplay ([#5054](https://github.com/streamlink/streamlink/pull/5054)) - Removed plugins: orf_tvthek ([#5104](https://github.com/streamlink/streamlink/pull/5104)) [Full changelog](https://github.com/streamlink/streamlink/compare/5.1.2...5.2.0) ## streamlink 5.1.2 (2022-12-03) Patch release: - Fixed: `ValueError` being raised while muxing streams ([#4998](https://github.com/streamlink/streamlink/pull/4998)) - Fixed: ad filtering bug in Twitch plugin ([#5007](https://github.com/streamlink/streamlink/pull/5007)) - Fixed: SVTPlay plugin ([#4994](https://github.com/streamlink/streamlink/pull/4994)) - Fixed: TVP plugin ([#4997](https://github.com/streamlink/streamlink/pull/4997)) - Docs: updated Linux AppImage and Windows builds install sections ([#4999](https://github.com/streamlink/streamlink/pull/4999)) - Docs: fixed man page links in HTML docs ([#4995](https://github.com/streamlink/streamlink/pull/4995)) [Full changelog](https://github.com/streamlink/streamlink/compare/5.1.1...5.1.2) ## streamlink 5.1.1 (2022-11-23) Patch release: - Changed: `WebsocketClient` to use CA certificates bundled by `certifi` ([#4977](https://github.com/streamlink/streamlink/pull/4977)) - Fixed: `SegmentedStreamReader` not properly getting closed ([#4972](https://github.com/streamlink/streamlink/pull/4972)) - Fixed: CLI argument links throughout the entire docs ([#4989](https://github.com/streamlink/streamlink/pull/4989)) - Build: added `certifi` as a direct dependency ([#4977](https://github.com/streamlink/streamlink/pull/4977)) [Full changelog](https://github.com/streamlink/streamlink/compare/5.1.0...5.1.1) ## streamlink 5.1.0 (2022-11-14) Release highlights: - Added: debug log messages of the FFmpeg version in use ([#4861](https://github.com/streamlink/streamlink/pull/4861)) Checking the FFmpeg version can be disabled via `--ffmpeg-no-validation` - Added: `--twitch-access-token-param` for changing access token API request params ([#4952](https://github.com/streamlink/streamlink/pull/4952)) - Added: new log level `all` ([#4941](https://github.com/streamlink/streamlink/pull/4941)) - Updated: sbscokr plugin and removed the `--sbscokr-id` parameter ([#4865](https://github.com/streamlink/streamlink/pull/4865)) - Updated: Twitch authentication docs ([#4940](https://github.com/streamlink/streamlink/pull/4940), [#4956](https://github.com/streamlink/streamlink/pull/4956)) - Fixed: broken `--twitch-disable-ads` mid-roll ad filering ([#4942](https://github.com/streamlink/streamlink/pull/4942)) - Fixed: incorrect module name for trace logs on Python 3.11 ([#4863](https://github.com/streamlink/streamlink/pull/4863)) - Fixed: bloomberg plugin ([#4919](https://github.com/streamlink/streamlink/pull/4919)) - Fixed: dailymotion plugin ([#4910](https://github.com/streamlink/streamlink/pull/4910)) - Fixed: raiplay plugin ([#4851](https://github.com/streamlink/streamlink/pull/4851)) - Fixed: tvp plugin ([#4905](https://github.com/streamlink/streamlink/pull/4905)) - Fixed: vinhlongtv plugin ([#4850](https://github.com/streamlink/streamlink/pull/4850)) - Fixed: various other plugin issues (see full changelog) - Removed plugins: egame ([#4866](https://github.com/streamlink/streamlink/pull/4866)) - Build: added `urllib3` as a direct dependency and set it to `>=1.26.0` ([#4950](https://github.com/streamlink/streamlink/pull/4950)) - Build: added `pytest-asyncio` to dev-requirements ([#4861](https://github.com/streamlink/streamlink/pull/4861)) [Full changelog](https://github.com/streamlink/streamlink/compare/5.0.1...5.1.0) ## streamlink 5.0.1 (2022-09-22) Patch release: - Fixed: truncation of relative paths in progress output on Windows ([#4830](https://github.com/streamlink/streamlink/pull/4830)) - Fixed: mitele plugin's validation schema ([#4839](https://github.com/streamlink/streamlink/pull/4839)) - Fixed: infinite loop in rtve plugin ([#4840](https://github.com/streamlink/streamlink/pull/4840)) [Full changelog](https://github.com/streamlink/streamlink/compare/5.0.0...5.0.1) ## streamlink 5.0.0 (2022-09-16) Breaking changes: - BREAKING: removed `avconv` (libav) from FFmpeg fallback list ([#4826](https://github.com/streamlink/streamlink/pull/4826)) - BREAKING/API: removed `Plugin.bind()` and changed the signature of the `Plugin` class constructor ([#4768](https://github.com/streamlink/streamlink/pull/4768)) A compatibility wrapper for these interface changes has temporarily been added in order to keep third-party plugin implementations working. [Please see the deprecation docs for more details.](https://streamlink.github.io/deprecations.html#deprecation-of-plugin-init-self-url) - BREAKING/API: changed the return value of `Session.resolve_url()` ([#4768](https://github.com/streamlink/streamlink/pull/4768)) [Please see the deprecation docs for more details.](https://streamlink.github.io/deprecations.html#session-resolve-url-return-type-changes) - BREAKING/API: removed `HTTPSession.parse_*()` methods ([#4803](https://github.com/streamlink/streamlink/pull/4803)) Release highlights: - Added: official support for Python 3.11 ([#4806](https://github.com/streamlink/streamlink/pull/4806)) - Added: `--player-external-http-continuous` ([#4739](https://github.com/streamlink/streamlink/pull/4739)) - Added: file path to progress output (`--output`, `--record`, etc.) ([#4764](https://github.com/streamlink/streamlink/pull/4764)) - Added: warning message when FFmpeg is not available and muxing is unsupported ([#4781](https://github.com/streamlink/streamlink/pull/4781)) - Changed: logging channel of deprecation messages to "warning" ([#4785](https://github.com/streamlink/streamlink/pull/4785)) - Disabled: `--twitch-disable-hosting` and removed its logic ([#4805](https://github.com/streamlink/streamlink/pull/4805)) - Fixed: memory leak when initializing the `Streamlink` session ([#4768](https://github.com/streamlink/streamlink/pull/4768)) - Fixed: cbsnews plugin ([#4743](https://github.com/streamlink/streamlink/pull/4743)) - Fixed: steam plugin authentication ([#4745](https://github.com/streamlink/streamlink/pull/4745)) - Fixed: ustreamtv plugin ([#4761](https://github.com/streamlink/streamlink/pull/4761)) - Fixed: huya plugin ([#4763](https://github.com/streamlink/streamlink/pull/4763)) - Fixed: atresplayer, mitele and rtve plugins ([#4759](https://github.com/streamlink/streamlink/pull/4759), [#4760](https://github.com/streamlink/streamlink/pull/4760), [#4766](https://github.com/streamlink/streamlink/pull/4766)) - Fixed: albavision, hiplayer and htv plugins ([#4770](https://github.com/streamlink/streamlink/pull/4770)) - Fixed: OKru plugin with support for the mobile page ([#4780](https://github.com/streamlink/streamlink/pull/4780)) - Fixed: trovo plugin VODs ([#4812](https://github.com/streamlink/streamlink/pull/4812)) - API: added `Streamlink` and `HTTPSession` typing informations to `Plugin` and `Stream` (including its various subclasses) ([#4802](https://github.com/streamlink/streamlink/pull/4802), [#4814](https://github.com/streamlink/streamlink/pull/4814)) - API: added `pluginargument` decorator ([#4747](https://github.com/streamlink/streamlink/pull/4747)) - Docs: updated `pluginmatcher` and `pluginargument` documentation ([#4771](https://github.com/streamlink/streamlink/pull/4771)) [Full changelog](https://github.com/streamlink/streamlink/compare/4.3.0...5.0.0) ## streamlink 4.3.0 (2022-08-15) Release highlights: - Improved: CLI download progress output ([#4656](https://github.com/streamlink/streamlink/pull/4656)) - Fixed: consecutive FFmpeg executable lookups not being cached ([#4660](https://github.com/streamlink/streamlink/pull/4660)) - Fixed: `--ffmpeg-verbose-path` not expanding `~` to the user's home directory ([#4688](https://github.com/streamlink/streamlink/pull/4688)) - Fixed: deprecated stdlib API calls in the upcoming Python 3.11 release ([#4651](https://github.com/streamlink/streamlink/pull/4651), [#4654](https://github.com/streamlink/streamlink/pull/4654)) - Fixed: huya plugin ([#4685](https://github.com/streamlink/streamlink/pull/4685)) - Fixed: livestream plugin ([#4679](https://github.com/streamlink/streamlink/pull/4679)) - Fixed: picarto plugin ([#4729](https://github.com/streamlink/streamlink/pull/4729)) - Fixed: nbcnews plugin ([#4668](https://github.com/streamlink/streamlink/pull/4668)) - Fixed: deutschewelle plugin ([#4725](https://github.com/streamlink/streamlink/pull/4725)) - Added plugins: atpchallenger ([#4700](https://github.com/streamlink/streamlink/pull/4700)) - Removed plugins: nbc + nbcsports + theplatform ([#4731](https://github.com/streamlink/streamlink/pull/4731)), common\_jwplayer ([#4733](https://github.com/streamlink/streamlink/pull/4733)) - Docs: various CLI related improvements ([#4659](https://github.com/streamlink/streamlink/pull/4659)) - Docs: removed OpenBSD and Ubuntu from install docs ([#4681](https://github.com/streamlink/streamlink/pull/4681)) - Plugin API: added new validation schemas and updated validators ([#4691](https://github.com/streamlink/streamlink/pull/4691), [#4709](https://github.com/streamlink/streamlink/pull/4709), [#4732](https://github.com/streamlink/streamlink/pull/4732)) [Full changelog](https://github.com/streamlink/streamlink/compare/4.2.0...4.3.0) ## streamlink 4.2.0 (2022-07-09) Release highlights: - Added: new Windows portable builds ([#4581](https://github.com/streamlink/streamlink/pull/4581)) - Added: more dependency versions to debug log header ([#4575](https://github.com/streamlink/streamlink/pull/4575)) - Added: parsed multivariant playlist reference to `HLSStream` and `MuxedHLSStream` ([#4568](https://github.com/streamlink/streamlink/pull/4568)) - Fixed: unnecessary delay when closing `DASHStream`s ([#4630](https://github.com/streamlink/streamlink/pull/4630)) - Fixed: `FFmpegMuxer` not closing sub-streams concurrently ([#4634](https://github.com/streamlink/streamlink/pull/4634)) - Fixed: threading issue when closing `WebsocketClient` connections ([#4608](https://github.com/streamlink/streamlink/pull/4608)) - Fixed: handling of `PluginError`s when outputting JSON data via `--json` ([#4590](https://github.com/streamlink/streamlink/pull/4590)) - Fixed: broken YouTube plugin when setting custom authentication headers ([#4576](https://github.com/streamlink/streamlink/pull/4576)) - Fixed: "source" Twitch VODs not being considered "best" ([#4625](https://github.com/streamlink/streamlink/pull/4625)) - Fixed: and rewritten FilmOn plugin ([#4573](https://github.com/streamlink/streamlink/pull/4573)) - Fixed: websocket issue in Twitcasting plugin ([#4608](https://github.com/streamlink/streamlink/pull/4608), [#4628](https://github.com/streamlink/streamlink/pull/4628)) - Fixed: VK plugin ([#4613](https://github.com/streamlink/streamlink/pull/4613), [#4638](https://github.com/streamlink/streamlink/pull/4638)) - Fixed: various other plugin issues (see full changelog) - New plugins: Aloula ([#4572](https://github.com/streamlink/streamlink/pull/4572)) - Removed plugins: Eltrecetv ([#4593](https://github.com/streamlink/streamlink/pull/4593)) - Docs: added openSUSE ([#4596](https://github.com/streamlink/streamlink/pull/4596)) and Scoop ([#4600](https://github.com/streamlink/streamlink/pull/4600)) packages - Docs: improved some links in CLI docs ([#4623](https://github.com/streamlink/streamlink/pull/4623)) - Docs: upgraded `furo` theme to `2022.06.04.1`, require `sphinx` `>=4`, and replace `recommonmark` with `myst-parser` ([#4610](https://github.com/streamlink/streamlink/pull/4610)) - Build: fixed outdated `python_requires` value in `setup.cfg` ([#4580](https://github.com/streamlink/streamlink/pull/4580)) - Build: upgraded `versioningit` build dependency to `>=2.0.0 <3` ([#4597](https://github.com/streamlink/streamlink/pull/4597)) [Full changelog](https://github.com/streamlink/streamlink/compare/4.1.0...4.2.0) ## streamlink 4.1.0 (2022-05-30) Release highlights: - Improved: decryption of HLS streams ([#4533](https://github.com/streamlink/streamlink/pull/4533)) - Improved: HLS playlist parsing ([#4540](https://github.com/streamlink/streamlink/pull/4540), [#4552](https://github.com/streamlink/streamlink/pull/4552)) - Improved: validation schemas and error handling/printing ([#4514](https://github.com/streamlink/streamlink/pull/4514)) - Improved: string representations of `Stream` implementations ([#4521](https://github.com/streamlink/streamlink/pull/4521)) - Fixed: new YouTube consent dialog ([#4515](https://github.com/streamlink/streamlink/pull/4515)) - Fixed: crunchyroll plugin ([#4510](https://github.com/streamlink/streamlink/pull/4510)) - Fixed: nicolive email logins ([#4553](https://github.com/streamlink/streamlink/pull/4553)) - Fixed: threading issue when closing segmented streams ([#4517](https://github.com/streamlink/streamlink/pull/4517)) - Removed: suppression of `InsecureRequestWarning` ([#4525](https://github.com/streamlink/streamlink/pull/4525)) - New plugins: blazetv ([#4548](https://github.com/streamlink/streamlink/pull/4548)), hiplayer ([#4507](https://github.com/streamlink/streamlink/pull/4507)), useetv ([#4536](https://github.com/streamlink/streamlink/pull/4536)) - Removed plugins: rotana ([#4512](https://github.com/streamlink/streamlink/pull/4512)) [Full changelog](https://github.com/streamlink/streamlink/compare/4.0.1...4.1.0) ## streamlink 4.0.1 (2022-05-01) No code changes. Please see the [changelog of the `4.0.0` release](https://streamlink.github.io/changelog.html#streamlink-4-0-0-2022-05-01), as it contains breaking changes. - Fixed: missing source-dist tarballs on GitHub release page ([#4503](https://github.com/streamlink/streamlink/pull/4503)) [Full changelog](https://github.com/streamlink/streamlink/compare/4.0.0...4.0.1) ## streamlink 4.0.0 (2022-05-01) Breaking changes: - BREAKING: dropped support for Python 3.6 ([#4442](https://github.com/streamlink/streamlink/pull/4442)) - BREAKING/API: removed [`streamlink.plugin.api.utils`](https://streamlink.github.io/deprecations.html#removal-of-streamlink-plugin-api-utils) module ([#4467](https://github.com/streamlink/streamlink/pull/4467)) - BREAKING/setup: switched to PEP 518 build system declaration and replaced versioneer in favor of versioningit ([#4440](https://github.com/streamlink/streamlink/pull/4440)) - BREAKING/packaging: replaced Windows installers with new ones built at [streamlink/windows-installer](https://github.com/streamlink/windows-installer) ([#4405](https://github.com/streamlink/streamlink/pull/4405)) - Added: new embedded Python builds for 3.8 and 3.10, both x86 and x86_64 - Updated: embedded FFmpeg to 5.0 Release highlights: - Added: support for `--record=-`, for writing data to stdout while watching at the same time ([#4462](https://github.com/streamlink/streamlink/pull/4462)) - Added: `plugin` variable for `--title`, `--output`, `--record` and `--record-and-pipe` ([#4437](https://github.com/streamlink/streamlink/pull/4437)) - Added: missing CLI protocol parameter support for DASH streams ([#4434](https://github.com/streamlink/streamlink/pull/4434)) - Updated: CLI and API documentation ([#4415](https://github.com/streamlink/streamlink/pull/4415), [#4424](https://github.com/streamlink/streamlink/pull/4424), [#4430](https://github.com/streamlink/streamlink/pull/4430)) - Updated: plugin description documentation ([#4391](https://github.com/streamlink/streamlink/pull/4391)) - Fixed: nicolive email logins ([#4380](https://github.com/streamlink/streamlink/pull/4380)) - Fixed: various other plugin issues (see the changelog down below) - New plugins: cmmedia ([#4416](https://github.com/streamlink/streamlink/pull/4416)), htv ([#4431](https://github.com/streamlink/streamlink/pull/4431)), mdstrm ([#4395](https://github.com/streamlink/streamlink/pull/4395)), trovo ([#4471](https://github.com/streamlink/streamlink/pull/4471)) - Removed plugins: abweb ([#4270](https://github.com/streamlink/streamlink/pull/4270)), garena ([#4460](https://github.com/streamlink/streamlink/pull/4460)), senategov ([#4458](https://github.com/streamlink/streamlink/pull/4458)), teamliquid ([#4393](https://github.com/streamlink/streamlink/pull/4393)), tlctr ([#4432](https://github.com/streamlink/streamlink/pull/4432)), vrtbe ([#4459](https://github.com/streamlink/streamlink/pull/4459)) [Full changelog](https://github.com/streamlink/streamlink/compare/3.2.0...4.0.0) ## streamlink 3.2.0 (2022-03-05) Release highlights: - Added: log message for the resolved path when writing output to file ([#4336](https://github.com/streamlink/streamlink/pull/4336)) - Added: new plugins for rtpa.es ([#4344](https://github.com/streamlink/streamlink/pull/4344)) and lnk.lt ([#4364](https://github.com/streamlink/streamlink/pull/4364)) - Changed: metadata requirements for built-in plugins ([#4374](https://github.com/streamlink/streamlink/pull/4374)) - Improved: plugins documentation ([#4374](https://github.com/streamlink/streamlink/pull/4374)) - Fixed: filmon plugin, requires at least OpenSSL 1.1.0 ([#4335](https://github.com/streamlink/streamlink/pull/4335), [#4345](https://github.com/streamlink/streamlink/pull/4345)) - Fixed: mildom plugin ([#4375](https://github.com/streamlink/streamlink/pull/4375)) - Fixed: nicolive email logins with confirmation codes ([#4380](https://github.com/streamlink/streamlink/pull/4380)) - Fixed: various other plugin issues, see the changelog down below - Upgraded: Windows installer's Python and dependency versions ([#4330](https://github.com/streamlink/streamlink/pull/4330), [#4347](https://github.com/streamlink/streamlink/pull/4347)) [Full changelog](https://github.com/streamlink/streamlink/compare/3.1.1...3.2.0) ## streamlink 3.1.1 (2022-01-25) Patch release: - Fixed: broken `streamlink.exe`/`streamlinkw.exe` executables in Windows installer ([#4308](https://github.com/streamlink/streamlink/pull/4308)) [Full changelog](https://github.com/streamlink/streamlink/compare/3.1.0...3.1.1) ## streamlink 3.1.0 (2022-01-22) Release highlights: - Changed: file overwrite prompt to wait for user input before opening streams ([#4252](https://github.com/streamlink/streamlink/pull/4252)) - Fixed: log messages appearing in `--json` output ([#4258](https://github.com/streamlink/streamlink/pull/4258)) - Fixed: keep-alive TCP connections when filtering out HLS segments ([#4229](https://github.com/streamlink/streamlink/pull/4229)) - Fixed: sort order of DASH streams with the same video resolution ([#4220](https://github.com/streamlink/streamlink/pull/4220)) - Fixed: HLS segment byterange offsets ([#4301](https://github.com/streamlink/streamlink/pull/4301), [#4302](https://github.com/streamlink/streamlink/pull/4302)) - Fixed: YouTube /live URLs ([#4222](https://github.com/streamlink/streamlink/pull/4222)) - Fixed: UStream websocket address ([#4238](https://github.com/streamlink/streamlink/pull/4238)) - Fixed: Pluto desync issues by filtering out bumper segments ([#4255](https://github.com/streamlink/streamlink/pull/4255)) - Fixed: various plugin issues - please see the changelog down below - Removed plugins: abweb ([#4270](https://github.com/streamlink/streamlink/pull/4270)), latina ([#4269](https://github.com/streamlink/streamlink/pull/4269)), live_russia_tv ([#4263](https://github.com/streamlink/streamlink/pull/4263)), liveme ([#4264](https://github.com/streamlink/streamlink/pull/4264)) [Full changelog](https://github.com/streamlink/streamlink/compare/3.0.3...3.1.0) ## streamlink 3.0.3 (2021-11-27) Patch release: - Fixed: broken output of the `--help` CLI argument ([#4213](https://github.com/streamlink/streamlink/pull/4213)) - Fixed: parsing of invalid HTML5 documents ([#4210](https://github.com/streamlink/streamlink/pull/4210)) Please see the [changelog of 3.0.0](https://streamlink.github.io/changelog.html#streamlink-3-0-0-2021-11-17), as it contains breaking changes that may require user interaction. [Full changelog](https://github.com/streamlink/streamlink/compare/3.0.2...3.0.3) ## streamlink 3.0.2 (2021-11-25) Patch release: - Added: support for the `id` plugin metadata property ([#4203](https://github.com/streamlink/streamlink/pull/4203)) - Updated: Twitch access token request parameter regarding embedded ads ([#4194](https://github.com/streamlink/streamlink/pull/4194)) - Fixed: early `SIGINT`/`SIGTERM` signal handling ([#4190](https://github.com/streamlink/streamlink/pull/4190)) - Fixed: broken character set decoding when parsing HTML documents ([#4201](https://github.com/streamlink/streamlink/pull/4201)) - Fixed: missing home directory expansion (tilde character) in file output paths ([#4204](https://github.com/streamlink/streamlink/pull/4204)) - New plugin: tviplayer ([#4199](https://github.com/streamlink/streamlink/pull/4199)) [Full changelog](https://github.com/streamlink/streamlink/compare/3.0.1...3.0.2) ## streamlink 3.0.1 (2021-11-17) Patch release: - Fixed: broken pycountry import in Windows installer's Python environment ([#4180](https://github.com/streamlink/streamlink/pull/4180)) [Full changelog](https://github.com/streamlink/streamlink/compare/3.0.0...3.0.1) ## streamlink 3.0.0 (2021-11-17) Breaking changes: - BREAKING: dropped support for RTMP, HDS and AkamaiHD streams ([#4169](https://github.com/streamlink/streamlink/pull/4169), [#4168](https://github.com/streamlink/streamlink/pull/4168)) - removed the `rtmp://`, `hds://` and `akamaihd://` protocol plugins - removed all Flash related code - upgraded all plugins using these old streaming protocols - dropped RTMPDump dependency - BREAKING: removed the following CLI arguments (and respective session options): ([#4169](https://github.com/streamlink/streamlink/pull/4169), [#4168](https://github.com/streamlink/streamlink/pull/4168)) - `--rtmp-rtmpdump`, `--rtmpdump`, `--rtmp-proxy`, `--rtmp-timeout` Users of Streamlink's Windows installer will need to update their [config file](https://streamlink.github.io/cli.html#configuration-file). - `--subprocess-cmdline`, `--subprocess-errorlog`, `--subprocess-errorlog-path` - `--hds-live-edge`, `--hds-segment-attempts`, `--hds-segment-threads`, `--hds-segment-timeout`, `--hds-timeout` - BREAKING: switched from HTTP to HTTPS for all kinds of scheme-less input URLs. If a site or http-proxy doesn't support HTTPS, then HTTP needs to be set explicitly. ([#4068](https://github.com/streamlink/streamlink/pull/4068), [#4053](https://github.com/streamlink/streamlink/pull/4053)) - BREAKING/API: changed `Session.resolve_url()` and `Session.resolve_url_no_redirect()` to return a tuple of a plugin class and the resolved URL instead of an initialized plugin class instance. This fixes the availability of plugin options in a plugin's constructor. ([#4163](https://github.com/streamlink/streamlink/pull/4163)) - BREAKING/requirements: dropped alternative dependency `pycrypto` and removed the `STREAMLINK_USE_PYCRYPTO` env var switch ([#4174](https://github.com/streamlink/streamlink/pull/4174)) - BREAKING/requirements: switched from `iso-639`+`iso3166` to `pycountry` and removed the `STREAMLINK_USE_PYCOUNTRY` env var switch ([#4175](https://github.com/streamlink/streamlink/pull/4175)) - BREAKING/setup: disabled unsupported Python versions, disabled the deprecated `test` setuptools command, removed the `NO_DEPS` env var, and switched to declarative package data via `setup.cfg` ([#4079](https://github.com/streamlink/streamlink/pull/4079), [#4107](https://github.com/streamlink/streamlink/pull/4107), [#4115](https://github.com/streamlink/streamlink/pull/4115), [#4113](https://github.com/streamlink/streamlink/pull/4113)) Release highlights: - Deprecated: `--https-proxy` in favor of a single `--http-proxy` CLI argument (and respective session option). Both now set the same proxy for all HTTPS/HTTP requests and websocket connections. [`--https-proxy` will be removed in a future release.](https://streamlink.github.io/deprecations.html#streamlink-3-0-0) ([#4120](https://github.com/streamlink/streamlink/pull/4120)) - Added: official support for Python 3.10 ([#4144](https://github.com/streamlink/streamlink/pull/4144)) - Added: `--twitch-api-header` for only setting Twitch.tv API requests headers (for authentication, etc.) as an alternative to `--http-header` ([#4156](https://github.com/streamlink/streamlink/pull/4156)) - Added: BASH and ZSH completions to sdist tarball and wheels. ([#4048](https://github.com/streamlink/streamlink/pull/4048), [#4178](https://github.com/streamlink/streamlink/pull/4178)) - Added: support for creating parent directories via metadata variables in file output paths ([#4085](https://github.com/streamlink/streamlink/pull/4085)) - Added: new WebsocketClient implementation ([#4153](https://github.com/streamlink/streamlink/pull/4153)) - Updated: plugins using websocket connections - nicolive, ustreamtv, twitcasting ([#4155](https://github.com/streamlink/streamlink/pull/4155), [#4164](https://github.com/streamlink/streamlink/pull/4164), [#4154](https://github.com/streamlink/streamlink/pull/4154)) - Updated: circumvention for YouTube's age verification ([#4058](https://github.com/streamlink/streamlink/pull/4058)) - Updated: and fixed lots of other plugins, see the detailed changelog below - Reverted: HLS segment downloads always being streamed, and added back `--hls-segment-stream-data` to prevent connection issues ([#4159](https://github.com/streamlink/streamlink/pull/4159)) - Fixed: URL percent-encoding for sites which require the lowercase format ([#4003](https://github.com/streamlink/streamlink/pull/4003)) - Fixed: XML parsing issues ([#4075](https://github.com/streamlink/streamlink/pull/4075)) - Fixed: broken `method` parameter when using the `httpstream://` protocol plugin ([#4171](https://github.com/streamlink/streamlink/pull/4171)) - Fixed: test failures when the `brotli` package is installed ([#4022](https://github.com/streamlink/streamlink/pull/4022)) - Requirements: bumped `lxml` to `>4.6.4,<5.0` and `websocket-client` to `>=1.2.1,<2.0` ([#4143](https://github.com/streamlink/streamlink/pull/4143), [#4153](https://github.com/streamlink/streamlink/pull/4153)) - Windows installer: upgraded Python to `3.9.8` and FFmpeg to `n4.4.1` ([#4176](https://github.com/streamlink/streamlink/pull/4176), [#4124](https://github.com/streamlink/streamlink/pull/4124)) - Documentation: upgraded to first stable version of the Furo theme ([#4000](https://github.com/streamlink/streamlink/pull/4000)) - New plugins: pandalive ([#4064](https://github.com/streamlink/streamlink/pull/4064)) - Removed plugins: tga ([#4129](https://github.com/streamlink/streamlink/pull/4129)), viasat ([#4087](https://github.com/streamlink/streamlink/pull/4087)), viutv ([#4018](https://github.com/streamlink/streamlink/pull/4018)), webcast_india_gov ([#4024](https://github.com/streamlink/streamlink/pull/4024)) [Full changelog](https://github.com/streamlink/streamlink/compare/2.4.0...3.0.0) ## streamlink 2.4.0 (2021-09-07) Release highlights: - Deprecated: stream-type specific stream transport options in favor of generic options ([#3893](https://github.com/streamlink/streamlink/pull/3893)) - use `--stream-segment-attempts` instead of `--{dash,hds,hls}-segment-attempts` - use `--stream-segment-threads` instead of `--{dash,hds,hls}-segment-threads` - use `--stream-segment-timeout` instead of `--{dash,hds,hls}-segment-timeout` - use `--stream-timeout` instead of `--{dash,hds,hls,rtmp,http-stream}-timeout` See the documentation's [deprecations page](https://streamlink.github.io/latest/deprecations.html#streamlink-2-4-0) for more information. - Deprecated: `--hls-segment-stream-data` option and made it always stream segment data ([#3894](https://github.com/streamlink/streamlink/pull/3894)) - Updated: Python version of the Windows installer from 3.8 to 3.9 and dropped support for Windows 7 due to Python incompatibilities ([#3918](https://github.com/streamlink/streamlink/pull/3918)) See the documentation's [install page](https://streamlink.github.io/install.html) for alternative installation methods on Windows 7. - Updated: FFmpeg in the Windows Installer from 4.2 (Zeranoe) to 4.4 ([streamlink/FFmpeg-Builds](https://github.com/streamlink/FFmpeg-Builds)) ([#3981](https://github.com/streamlink/streamlink/pull/3981)) - Added: `{author}`, `{category}`/`{game}`, `{title}` and `{url}` variables to `--output`, `--record` and `--record-and-play` ([#3962](https://github.com/streamlink/streamlink/pull/3962)) - Added: `{time}`/`{time:custom-format}` variable to `--title`, `--output`, `--record` and `--record-and-play` ([#3993](https://github.com/streamlink/streamlink/pull/3993)) - Added: `--fs-safe-rules` for changing character replacement rules in file outputs ([#3962](https://github.com/streamlink/streamlink/pull/3962)) - Added: plugin metadata to `--json` stream data output ([#3987](https://github.com/streamlink/streamlink/pull/3987)) - Fixed: named pipes not being cleaned up by FFMPEGMuxer ([#3992](https://github.com/streamlink/streamlink/pull/3992)) - Fixed: KeyError on invalid variables in `--player-args` ([#3988](https://github.com/streamlink/streamlink/pull/3988)) - Fixed: tests failing in certain cases when run in different order ([#3920](https://github.com/streamlink/streamlink/pull/3920)) - Fixed: initial HLS playlist parsing issues ([#3903](https://github.com/streamlink/streamlink/pull/3903), [#3910](https://github.com/streamlink/streamlink/pull/3910)) - Fixed: various plugin issues. Please see the changelog down below. - Dependencies: added `lxml>=4.6.3` ([#3952](https://github.com/streamlink/streamlink/pull/3952)) - Dependencies: switched back to `requests>=2.26.0` on Windows ([#3930](https://github.com/streamlink/streamlink/pull/3930)) - Removed plugins: animeworld ([#3951](https://github.com/streamlink/streamlink/pull/3951)), gardenersworld ([#3966](https://github.com/streamlink/streamlink/pull/3966)), huomao ([#3932](https://github.com/streamlink/streamlink/pull/3932)) [Full changelog](https://github.com/streamlink/streamlink/compare/2.3.0...2.4.0) ## streamlink 2.3.0 (2021-07-26) Release highlights: - Implemented: new plugin URL matching API ([#3814](https://github.com/streamlink/streamlink/issues/3814), [#3821](https://github.com/streamlink/streamlink/pull/3821)) Third-party plugins which use the old API will still be resolved, but those plugins will have to upgrade in the future. See the documentation's [deprecations page](https://streamlink.github.io/latest/deprecations.html#streamlink-2-3-0) for more information. - Implemented: HLS media initialization section (fragmented MPEG-4 streams) ([#3828](https://github.com/streamlink/streamlink/pull/3828)) - Upgraded: `requests` to `>=2.26.0,<3` and set it to `==2.25.1` on Windows ([#3864](https://github.com/streamlink/streamlink/pull/3864), [#3880](https://github.com/streamlink/streamlink/pull/3880)) - Fixed: YouTube channel URLs, premiering live streams, added API fallback ([#3847](https://github.com/streamlink/streamlink/pull/3847), [#3873](https://github.com/streamlink/streamlink/pull/3873), [#3809](https://github.com/streamlink/streamlink/pull/3809)) - Removed plugins: canalplus ([#3841](https://github.com/streamlink/streamlink/pull/3841)), dommune ([#3818](https://github.com/streamlink/streamlink/pull/3818)), liveedu ([#3845](https://github.com/streamlink/streamlink/pull/3845)), periscope ([#3813](https://github.com/streamlink/streamlink/pull/3813)), powerapp ([#3816](https://github.com/streamlink/streamlink/pull/3816)), rtlxl ([#3842](https://github.com/streamlink/streamlink/pull/3842)), streamingvideoprovider ([#3843](https://github.com/streamlink/streamlink/pull/3843)), teleclubzoom ([#3817](https://github.com/streamlink/streamlink/pull/3817)), tigerdile ([#3819](https://github.com/streamlink/streamlink/pull/3819)) [Full changelog](https://github.com/streamlink/streamlink/compare/2.2.0...2.3.0) ## streamlink 2.2.0 (2021-06-19) Release highlights: - Changed: default config file path on macOS and Windows ([#3766](https://github.com/streamlink/streamlink/pull/3766)) - macOS: `${HOME}/Library/Application Support/streamlink/config` - Windows: `%APPDATA%\streamlink\config` - Changed: default custom plugins directory path on macOS and Linux/BSD ([#3766](https://github.com/streamlink/streamlink/pull/3766)) - macOS: `${HOME}/Library/Application Support/streamlink/plugins` - Linux/BSD: `${XDG_DATA_HOME:-${HOME}/.local/share}/streamlink/plugins` - Deprecated: old config file paths and old custom plugins directory paths ([#3784](https://github.com/streamlink/streamlink/pull/3784)) - Windows: - `%APPDATA%\streamlink\streamlinkrc` - macOS: - `${XDG_CONFIG_HOME:-${HOME}/.config}/streamlink/config` - `${XDG_CONFIG_HOME:-${HOME}/.config}/streamlink/plugins` - `${HOME}/.streamlinkrc` - Linux/BSD: - `${XDG_CONFIG_HOME:-${HOME}/.config}/streamlink/plugins` - `${HOME}/.streamlinkrc` Support for these old paths will be dropped in the future. See the [CLI documentation](https://streamlink.github.io/cli.html) for all the details regarding these changes. - Implemented: `--logfile` CLI argument ([#3753](https://github.com/streamlink/streamlink/pull/3753)) - Fixed: Youtube 404 errors by dropping private API calls (plugin rewrite) ([#3797](https://github.com/streamlink/streamlink/pull/3797)) - Fixed: Twitch clips ([#3762](https://github.com/streamlink/streamlink/pull/3762), [#3775](https://github.com/streamlink/streamlink/pull/3775)) and hosted channel redirection ([#3776](https://github.com/streamlink/streamlink/pull/3776)) - Fixed: Olympicchannel plugin ([#3760](https://github.com/streamlink/streamlink/pull/3760)) - Fixed: various Zattoo plugin issues ([#3773](https://github.com/streamlink/streamlink/pull/3773), [#3780](https://github.com/streamlink/streamlink/pull/3780)) - Fixed: HTTP responses with truncated body and mismatching content-length header ([#3768](https://github.com/streamlink/streamlink/pull/3768)) - Fixed: scheme-less URLs with address:port for `--http-proxy`, etc. ([#3765](https://github.com/streamlink/streamlink/pull/3765)) - Fixed: rendered man page path on Sphinx 4 ([#3750](https://github.com/streamlink/streamlink/pull/3750)) - Added plugins: mildom.com ([#3584](https://github.com/streamlink/streamlink/pull/3584)), booyah.live ([#3585](https://github.com/streamlink/streamlink/pull/3585)), mediavitrina.ru ([#3743](https://github.com/streamlink/streamlink/pull/3743)) - Removed plugins: ine.com ([#3781](https://github.com/streamlink/streamlink/pull/3781)), playtv.fr ([#3798](https://github.com/streamlink/streamlink/pull/3798)) [Full changelog](https://github.com/streamlink/streamlink/compare/2.1.2...2.2.0) ## streamlink 2.1.2 (2021-05-20) Patch release: - Fixed: youtube 404 errors ([#3732](https://github.com/streamlink/streamlink/pull/3732)), consent dialog ([#3672](https://github.com/streamlink/streamlink/pull/3672)) and added short URLs ([#3677](https://github.com/streamlink/streamlink/pull/3677)) - Fixed: picarto plugin ([#3661](https://github.com/streamlink/streamlink/pull/3661)) - Fixed: euronews plugin ([#3698](https://github.com/streamlink/streamlink/pull/3698)) - Fixed: bbciplayer plugin ([#3725](https://github.com/streamlink/streamlink/pull/3725)) - Fixed: missing removed-plugins-file in `setup.py build` ([#3653](https://github.com/streamlink/streamlink/pull/3653)) - Changed: HLS streams to use rounded bandwidth names ([#3721](https://github.com/streamlink/streamlink/pull/3721)) - Removed: plugin for hitbox.tv / smashcast.tv ([#3686](https://github.com/streamlink/streamlink/pull/3686)), tvplayer.com ([#3673](https://github.com/streamlink/streamlink/pull/3673)) [Full changelog](https://github.com/streamlink/streamlink/compare/2.1.1...2.1.2) ## streamlink 2.1.1 (2021-03-25) Patch release: - Fixed: test failure due to missing removed plugins file in sdist tarball ([#3644](https://github.com/streamlink/streamlink/pull/3644)). [Full changelog](https://github.com/streamlink/streamlink/compare/2.1.0...2.1.1) ## streamlink 2.1.0 (2021-03-22) Release highlights: - Added: `--interface`, `-4` / `--ipv4` and `-6` / `--ipv6` ([#3483](https://github.com/streamlink/streamlink/pull/3483)) - Added: `--niconico-purge-credentials` ([#3434](https://github.com/streamlink/streamlink/pull/3434)) - Added: `--twitcasting-password` ([#3505](https://github.com/streamlink/streamlink/pull/3505)) - Added: Linux AppImages ([#3611](https://github.com/streamlink/streamlink/pull/3611)) - Added: pre-built man page to bdist wheels and sdist tarballs ([#3459](https://github.com/streamlink/streamlink/pull/3459), [#3510](https://github.com/streamlink/streamlink/pull/3510)) - Added: plugin for ahaber.com.tr and atv.com.tr ([#3484](https://github.com/streamlink/streamlink/pull/3484)), nimo.tv ([#3508](https://github.com/streamlink/streamlink/pull/3508)) - Fixed: `--player-http` / `--player-continuous-http` HTTP server being bound to all interfaces ([#3450](https://github.com/streamlink/streamlink/pull/3450)) - Fixed: handling of languages without alpha_2 code when using pycountry ([#3518](https://github.com/streamlink/streamlink/pull/3518)) - Fixed: memory leak when calling `streamlink.streams()` ([#3486](https://github.com/streamlink/streamlink/pull/3486)) - Fixed: race condition in HLS related tests ([#3454](https://github.com/streamlink/streamlink/pull/3454)) - Fixed: `--player-fifo` issues on Windows with VLC or MPV ([#3619](https://github.com/streamlink/streamlink/pull/3619)) - Fixed: various plugins issues (see detailed changelog down below) - Removed: Windows portable (RosadinTV) ([#3535](https://github.com/streamlink/streamlink/pull/3535)) - Removed: plugin for micous.com ([#3457](https://github.com/streamlink/streamlink/pull/3457)), ntvspor.net ([#3485](https://github.com/streamlink/streamlink/pull/3485)), btsports ([#3636](https://github.com/streamlink/streamlink/pull/3636)) - Dependencies: set `websocket-client` to `>=0.58.0` ([#3634](https://github.com/streamlink/streamlink/pull/3634)) [Full changelog](https://github.com/streamlink/streamlink/compare/2.0.0...2.1.0) ## streamlink 2.0.0 (2020-12-22) Release highlights: - BREAKING: dropped support for Python 2 and Python 3.5 ([#3232](https://github.com/streamlink/streamlink/pull/3232), [#3269](https://github.com/streamlink/streamlink/pull/3269)) - BREAKING: updated the Python version of the Windows installer to 3.8 ([#3330](https://github.com/streamlink/streamlink/pull/3330)) Users of Windows 7 will need their system to be fully upgraded. - BREAKING: removed all deprecated CLI arguments ([#3277](https://github.com/streamlink/streamlink/pull/3277), [#3349](https://github.com/streamlink/streamlink/pull/3349)) - `--http-cookies`, `--http-headers`, `--http-query-params` - `--no-version-check` - `--rtmpdump-proxy` - `--cmdline`, `-c` - `--errorlog`, `-e` - `--errorlog-path` - `--btv-username`, `--btv-password` - `--crunchyroll-locale` - `--pixiv-username`, `--pixiv-password` - `--twitch-oauth-authenticate`, `--twitch-oauth-token`, `--twitch-cookie` - `--ustvnow-station-code` - `--youtube-api-key` - BREAKING: replaced various subtitle muxing CLI arguments with `--mux-subtitles` ([#3324](https://github.com/streamlink/streamlink/pull/3324)) - `--funimationnow-mux-subtitles` - `--pluzz-mux-subtitles` - `--rtve-mux-subtitles` - `--svtplay-mux-subtitles` - `--vimeo-mux-subtitles` - BREAKING: sideloading faulty plugins will now raise an `Exception` ([#3366](https://github.com/streamlink/streamlink/pull/3366)) - BREAKING: changed trace logging timestamp format ([#3273](https://github.com/streamlink/streamlink/pull/3273)) - BREAKING/API: removed deprecated `Session` compat options ([#3349](https://github.com/streamlink/streamlink/pull/3349)) - BREAKING/API: removed deprecated custom `Logger` and `LogRecord` ([#3273](https://github.com/streamlink/streamlink/pull/3273)) - BREAKING/API: removed deprecated parameters from `HLSStream.parse_variant_playlist` ([#3347](https://github.com/streamlink/streamlink/pull/3347)) - BREAKING/API: removed `plugin.api.support_plugin` ([#3398](https://github.com/streamlink/streamlink/pull/3398)) - Added: new plugin for pluto.tv ([#3363](https://github.com/streamlink/streamlink/pull/3363)) - Added: support for HLS master playlist URLs to `--stream-url` / `--json` ([#3300](https://github.com/streamlink/streamlink/pull/3300)) - Added: `--ffmpeg-fout` for changing the output format of muxed streams ([#2892](https://github.com/streamlink/streamlink/pull/2892)) - Added: `--ffmpeg-copyts` and `--ffmpeg-start-at-zero` ([#3404](https://github.com/streamlink/streamlink/pull/3404), [#3413](https://github.com/streamlink/streamlink/pull/3413)) - Added: `--streann-url` for iframe referencing ([#3356](https://github.com/streamlink/streamlink/pull/3356)) - Added: `--niconico-timeshift-offset` ([#3425](https://github.com/streamlink/streamlink/pull/3425)) - Fixed: duplicate stream names in DASH inputs ([#3410](https://github.com/streamlink/streamlink/pull/3410)) - Fixed: youtube live playback ([#3268](https://github.com/streamlink/streamlink/pull/3268), [#3372](https://github.com/streamlink/streamlink/pull/3372), [#3428](https://github.com/streamlink/streamlink/pull/3428)) - Fixed: `--twitch-disable-reruns` ([#3375](https://github.com/streamlink/streamlink/pull/3375)) - Fixed: various plugins issues (see detailed changelog down below) - Changed: `{filename}` variable in `--player-args` / `-a` to `{playerinput}` and made both optional ([#3313](https://github.com/streamlink/streamlink/pull/3313)) - Changed: and fixed `streamlinkrc` config file in the Windows installer ([#3350](https://github.com/streamlink/streamlink/pull/3350)) - Changed: MPV's automated `--title` argument to `--force-media-title` ([#3405](https://github.com/streamlink/streamlink/pull/3405)) - Changed: HTML documentation theme to [furo](https://github.com/pradyunsg/furo) ([#3335](https://github.com/streamlink/streamlink/pull/3335)) - Removed: plugins for `skai`, `kingkong`, `ellobo`, `trt`/`trtspor`, `tamago`, `streamme`, `metube`, `cubetv`, `willax` [Full changelog](https://github.com/streamlink/streamlink/compare/1.7.0...2.0.0) ## streamlink 1.7.0 (2020-10-18) Release highlights: - Added: new plugins for micous.com, tv999.bg and cbsnews.com - Added: new embedded ad detection for Twitch streams ([#3213](https://github.com/streamlink/streamlink/pull/3213)) - Fixed: a few broken plugins and minor plugin issues (see changelog down below) - Fixed: arguments in config files were read too late before taking effect ([#3255](https://github.com/streamlink/streamlink/pull/3255)) - Fixed: Arte plugin returning too many streams and overriding primary ones ([#3228](https://github.com/streamlink/streamlink/pull/3228)) - Fixed: Twitch plugin error when stream metadata API response is empty ([#3223](https://github.com/streamlink/streamlink/pull/3223)) - Fixed: Zattoo login issues ([#3202](https://github.com/streamlink/streamlink/pull/3202)) - Changed: plugin request and submission guidelines ([#3244](https://github.com/streamlink/streamlink/pull/3244)) - Changed: refactored and cleaned up Twitch plugin ([#3227](https://github.com/streamlink/streamlink/pull/3227)) - Removed: `platform=_` stream token request parameter from Twitch plugin (again) ([#3220](https://github.com/streamlink/streamlink/pull/3220)) - Removed: plugins for itvplayer, aljazeeraen, srgssr and dingittv [Full changelog](https://github.com/streamlink/streamlink/compare/1.6.0...1.7.0) ## streamlink 1.6.0 (2020-09-22) Release highlights: - Fixed: lots of broken plugins and minor plugin issues (see changelog down below) - Fixed: embedded ads on Twitch with an ads workaround, removing pre-roll and mid-stream ads ([#3173](https://github.com/streamlink/streamlink/pull/3173)) - Fixed: read timeout error when filtering out HLS segments ([#3187](https://github.com/streamlink/streamlink/pull/3187)) - Fixed: twitch plugin logging incorrect low-latency status when pre-roll ads exist ([#3169](https://github.com/streamlink/streamlink/pull/3169)) - Fixed: crunchyroll auth logic ([#3150](https://github.com/streamlink/streamlink/pull/3150)) - Added: the `--hls-playlist-reload-time` parameter for customizing HLS playlist reload times ([#2925](https://github.com/streamlink/streamlink/pull/2925)) - Added: `python -m streamlink` invocation style support ([#3174](https://github.com/streamlink/streamlink/pull/3174)) - Added: plugin for mrt.com.mk ([#3097](https://github.com/streamlink/streamlink/pull/3097)) - Changed: yupptv plugin and replaced email+pass with id+token authentication ([#3116](https://github.com/streamlink/streamlink/pull/3116)) - Removed: plugins for vaughnlive, pandatv, douyutv, cybergame, europaplus and startv [Full changelog](https://github.com/streamlink/streamlink/compare/1.5.0...1.6.0) ## streamlink 1.5.0 (2020-07-07) A minor release with fixes for `pycountry==20.7.3` ([#3057](https://github.com/streamlink/streamlink/pull/3057)) and a few plugin additions and removals. And of course the usual plugin fixes and upgrades, which you can see in the git shortlog down below. Thank you to everyone involved! Support for Python2 has not been dropped yet (contrary to the comment in the last changelog), but will be in the near future. [Full changelog](https://github.com/streamlink/streamlink/compare/1.4.1...1.5.0) ## streamlink 1.4.1 (2020-04-24) No code changes. [See the full `1.4.0` changelog here.](https://github.com/streamlink/streamlink/releases/tag/1.4.0) [Full changelog](https://github.com/streamlink/streamlink/compare/1.4.0...1.4.1) ## streamlink 1.4.0 (2020-04-22) This will be the last release with support for Python 2, as it has finally reached its EOL at the beginning of this year. Streamlink 1.4.0 comes with lots of plugin fixes/improvements, as well as some new features and plugins, and also a few plugin removals. Notable changes: - New: low latency streaming on Twitch via `--twitch-low-latency` ([#2513](https://github.com/streamlink/streamlink/pull/2513)) - New: output HLS segment data immediately via `--hls-segment-stream-data` ([#2513](https://github.com/streamlink/streamlink/pull/2513)) - New: always show download progress via `--force-progress` ([#2438](https://github.com/streamlink/streamlink/pull/2438)) - New: URL template support for `--hls-segment-key-uri` ([#2821](https://github.com/streamlink/streamlink/pull/2821)) - Removed: Twitch auth logic, `--twitch-oauth-token`, `--twitch-oauth-authenticate`, `--twitch-cookie` ([#2846](https://github.com/streamlink/streamlink/pull/2846)) - Fixed: Youtube plugin ([#2858](https://github.com/streamlink/streamlink/pull/2858)) - Fixed: Crunchyroll plugin ([#2788](https://github.com/streamlink/streamlink/pull/2788)) - Fixed: Pixiv plugin ([#2840](https://github.com/streamlink/streamlink/pull/2840)) - Fixed: TVplayer plugin ([#2802](https://github.com/streamlink/streamlink/pull/2802)) - Fixed: Zattoo plugin ([#2887](https://github.com/streamlink/streamlink/pull/2887)) - Changed: set Firefox User-Agent HTTP header by default ([#2795](https://github.com/streamlink/streamlink/pull/2795)) - Changed: upgraded bundled FFmpeg to `4.2.2` in Windows installer ([#2916](https://github.com/streamlink/streamlink/pull/2916)) [Full changelog](https://github.com/streamlink/streamlink/compare/1.3.1...1.4.0) ## streamlink 1.3.1 (2020-01-27) A small patch release that addresses the removal of [MPV's legacy option syntax](https://mpv.io/manual/master/#legacy-option-syntax), also with fixes of several plugins, the addition of the `--twitch-disable-reruns` parameter and dropped support for Python 3.4. [Full changelog](https://github.com/streamlink/streamlink/compare/1.3.0...1.3.1) ## streamlink 1.3.0 (2019-11-22) A new release with plugin updates and fixes, including Twitch.tv (see [#2680](https://github.com/streamlink/streamlink/issues/2680)), which had to be delayed due to back and forth API changes. The Twitch.tv workarounds mentioned in [#2680](https://github.com/streamlink/streamlink/issues/2680) don't have to be applied anymore, but authenticating via `--twitch-oauth-token` has been disabled, regardless of the origin of the OAuth token (via `--twitch-oauth-authenticate` or the Twitch website). In order to not introduce breaking changes, both parameters have been kept in this release and the user name will still be logged when using an OAuth token, but receiving item drops or accessing restricted streams is not possible anymore. Plugins for the following sites have also been added: - albavision - news.now.com - twitcasting.tv - viu.tv - vlive.tv - willax.tv [Full changelog](https://github.com/streamlink/streamlink/compare/1.2.0...1.3.0) ## streamlink 1.2.0 (2019-08-18) Here are the changes for this month's release - Multiple plugin fixes - Fixed single hyphen params at the beginning of --player-args (#2333) - `--http-proxy` will set the default value of `--https-proxy` to same as `--http-proxy`. (#2536) - DASH Streams will handle headers correctly (#2545) - the timestamp for FFMPEGMuxer streams will start with zero (#2559) [Full changelog](https://github.com/streamlink/streamlink/compare/1.1.1...1.2.0) ## streamlink 1.1.1 (2019-04-02) This is just a small patch release which fixes a build/deploy issue with the new special wheels for Windows on PyPI. (#2392) [Please see the full changelog of the `1.1.0` release!](https://github.com/streamlink/streamlink/releases/tag/1.1.0) [Full changelog](https://github.com/streamlink/streamlink/compare/1.1.0...1.1.1) ## streamlink 1.1.0 (2019-03-31) These are the highlights of Streamlink's first minor release after the 1.0.0 milestone: - several plugin fixes, improvements and new plugin implementations - addition of the `--twitch-disable-ads` parameter for filtering out advertisement segments from Twitch.tv streams (#2372) - DASH stream improvements (#2285) - documentation enhancements (#2292, #2293) - addition of the `{url}` player title variable (#2232) - default player title config for PotPlayer (#2224) - new `streamlinkw` executable on Windows (wheels + installer) (#2326) - Github release assets simplification (#2360) [Full changelog](https://github.com/streamlink/streamlink/compare/1.0.0...1.1.0) ## streamlink 1.0.0 (2019-01-30) The celebratory release of Streamlink 1.0.0! *A lot* of hard work has gone into getting Streamlink to where it is. Not only is Streamlink used across multiple applications and platforms, but companies as well. Streamlink started from the inaugural [fork of Livestreamer](https://github.com/chrippa/livestreamer/issues/1427) on September 17th, 2016. Since then, We've hit multiple milestones: - Over 886 PRs - Hit 3,000 commits in Streamlink - Obtaining our first sponsors as well as backers of the project - The creation of our own logo (https://github.com/streamlink/streamlink/issues/1123) Thanks to everyone who has contributed to Streamlink (and our backers)! Without you, we wouldn't be where we are today. **Without further ado, here are the changes in release 1.0.0:** - We have a new icon / logo for Streamlink! (https://github.com/streamlink/streamlink/pull/2165) - Updated dependencies (https://github.com/streamlink/streamlink/pull/2230) - A *ton* of plugin updates. Have a look at [this search query](https://github.com/streamlink/streamlink/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Aclosed+plugins.+) for all the recent updates. - You can now provide a custom key URI to override HLS streams (https://github.com/streamlink/streamlink/pull/2139). For example: `--hls-segment-key-uri ` - User agents for API communication have been updated (https://github.com/streamlink/streamlink/pull/2194) - Special synonyms have been added to sort "best" and "worst" streams (https://github.com/streamlink/streamlink/pull/2127). For example: `streamlink --stream-sorting-excludes '>=480p' URL best,best-unfiltered` - Process output will no longer show if tty is unavailable (https://github.com/streamlink/streamlink/pull/2090) - We've removed BountySource in favour of our OpenCollective page. If you have any features you'd like to request, please open up an issue with the request and possibly consider backing us! - Improved terminal progress display for wide characters (https://github.com/streamlink/streamlink/pull/2032) - Fixed a bug with dynamic playlists on playback (https://github.com/streamlink/streamlink/pull/2096) - Fixed makeinstaller.sh (https://github.com/streamlink/streamlink/pull/2098) - Old Livestreamer deprecations and API references were removed (https://github.com/streamlink/streamlink/pull/1987) - Dependencies have been updated for Python (https://github.com/streamlink/streamlink/pull/1975) - Newer and more common User-Agents are now used (https://github.com/streamlink/streamlink/pull/1974) - DASH stream bitrates now round-up to the nearest 10, 100, 1000, etc. (https://github.com/streamlink/streamlink/pull/1995) - Updated documentation on issue templates (https://github.com/streamlink/streamlink/pull/1996) - URL have been added for better processing of HTML tags (https://github.com/streamlink/streamlink/pull/1675) - Fixed sort and prog issue (https://github.com/streamlink/streamlink/pull/1964) - Reformatted issue templates (https://github.com/streamlink/streamlink/pull/1966) - Fixed crashing bug with player-continuous-http option (https://github.com/streamlink/streamlink/pull/2234) - Make sure all dev dependencies (https://github.com/streamlink/streamlink/pull/2235) - -r parameter has been replaced for --rtmp-rtmpdump (https://github.com/streamlink/streamlink/pull/2152) **Breaking changes:** - A large number of unmaintained or NSFW plugins have been removed. You can find the PR that implemented that change here: https://github.com/streamlink/streamlink/pull/2003 . See our [CONTRIBUTING.md](https://github.com/streamlink/streamlink/blob/130489c6f5ad15488cd4ff7a25c74bf070f163ec/CONTRIBUTING.md) documentation for plugin policy. [Full changelog](https://github.com/streamlink/streamlink/compare/0.14.2...1.0.0) ## streamlink 0.14.2 (2018-06-28) Just a few small fixes in this release. - Fixed Twitch OAuth request flow (https://github.com/streamlink/streamlink/pull/1856) - Fix the tv3cat and vk plugins (https://github.com/streamlink/streamlink/pull/1851, https://github.com/streamlink/streamlink/pull/1874) - VOD supported added to atresplayer plugin (https://github.com/streamlink/streamlink/pull/1852, https://github.com/streamlink/streamlink/pull/1853) - Removed tv8cati and nineanime plugins (https://github.com/streamlink/streamlink/pull/1860, https://github.com/streamlink/streamlink/pull/1863) - Added mjunoon.tv plugin (https://github.com/streamlink/streamlink/pull/1857) [Full changelog](https://github.com/streamlink/streamlink/compare/0.14.0...0.14.2) ## streamlink 0.14.0 (2018-06-26) Here are the changes to this months release! - Multiple plugin fixes - Bug fixes for DASH streams (https://github.com/streamlink/streamlink/pull/1846) - Updated API call for api.utils hours_minutes_seconds (https://github.com/streamlink/streamlink/pull/1804) - Updated documentation (https://github.com/streamlink/streamlink/pull/1826) - Dict structures fix (https://github.com/streamlink/streamlink/pull/1792) - Reformated help menu (https://github.com/streamlink/streamlink/pull/1754) - Logger fix (https://github.com/streamlink/streamlink/pull/1773) [Full changelog](https://github.com/streamlink/streamlink/compare/0.13.0...0.14.0) ## streamlink 0.13.0 (2018-06-06) Massive release this month! Here are the changes: - Initial MPEG DASH support has been added! (https://github.com/streamlink/streamlink/pull/1637) Many thanks to @beardypig - As always, a *ton* of plugin updates - Updates to our documentation (https://github.com/streamlink/streamlink/pull/1673) - Updates to our logging (https://github.com/streamlink/streamlink/pull/1752) as well as log --quiet options (https://github.com/streamlink/streamlink/pull/1744) (https://github.com/streamlink/streamlink/pull/1720) - Our release script has been updated (https://github.com/streamlink/streamlink/pull/1711) - Support for livestreams when using the `--hls-duration` option (https://github.com/streamlink/streamlink/pull/1710) - Allow streamlink to exit faster when using Ctrl+C (https://github.com/streamlink/streamlink/pull/1658) - Added an OpenCV Face Detection example (https://github.com/streamlink/streamlink/pull/1689) [Full changelog](https://github.com/streamlink/streamlink/compare/0.12.1...0.13.0) ## streamlink 0.12.1 (2018-05-07) Streamlink 0.12.1 Small release to fix a pip / Windows.exe generation bug! [Full changelog](https://github.com/streamlink/streamlink/compare/0.12.0...0.12.1) ## streamlink 0.12.0 (2018-05-07) Streamlink 0.12.0 Thanks for all the contributors to this month's release! New updates: - A *ton* of plugin updates (like always! see below for a list of updates) - Ignoring a bunch of useless files when developing (https://github.com/streamlink/streamlink/pull/1570) - A new option to limit the number of fetch retries (https://github.com/streamlink/streamlink/pull/1375) - YouTube has been updated to not use MuxedStream for livestreams (https://github.com/streamlink/streamlink/pull/1556) - Bug fix with ffmpegmux (https://github.com/streamlink/streamlink/pull/1502) - Removed dead plugins and deprecated options (https://github.com/streamlink/streamlink/pull/1546) [Full changelog](https://github.com/streamlink/streamlink/compare/0.11.0...0.12.0) ## streamlink 0.11.0 (2018-03-08) Streamlink 0.11.0! Here's what's new: - Fixed documentation (https://github.com/streamlink/streamlink/pull/1467 and https://github.com/streamlink/streamlink/pull/1468) - Current versions of the OS, Python, Streamlink and Requests are now shown with -l debug (https://github.com/streamlink/streamlink/pull/1374) - ok.ru/live plugin added (https://github.com/streamlink/streamlink/pull/1451) - New option --hls-segment-ignore-names (https://github.com/streamlink/streamlink/pull/1432) - AfreecaTV plugin updates (https://github.com/streamlink/streamlink/pull/1390) - Added support for zattoo recordings (https://github.com/streamlink/streamlink/pull/1480) - Bigo plugin updates (https://github.com/streamlink/streamlink/pull/1474) - Neulion plugin removed due to DMCA notice (https://github.com/streamlink/streamlink/pull/1497) - And many more updates to numerous other plugins! [Full changelog](https://github.com/streamlink/streamlink/compare/0.10.0...0.11.0) ## streamlink 0.10.0 (2018-01-23) Streamlink 0.10.0! There's been a lot of activity since our November release. Changes: - Multiple plugin updates (too many to list, see below for the plugin changes!) - HLS seeking support (https://github.com/streamlink/streamlink/pull/1303) - Changes to the Windows binary (docs: https://github.com/streamlink/streamlink/pull/1408 minor changes to install directory: https://github.com/streamlink/streamlink/pull/1407) [Full changelog](https://github.com/streamlink/streamlink/compare/0.9.0...0.10.0) ## streamlink 0.9.0 (2017-11-14) Streamlink 0.9.0 has been released! This release is mostly code refactoring as well as module inclusion. Features: - Updates to multiple plugins (electrecetv, tvplayer, Teve2, cnnturk, kanald) - SOCKS module being included in the Streamlink installer (PySocks) Many thanks to those who've contributed in this release! [Full changelog](https://github.com/streamlink/streamlink/compare/0.8.1...0.9.0) ## streamlink 0.8.1 (2017-09-12) 0.8.1 of Streamlink! 97 commits have occurred since the last release, including a large majority of plugin changes. Here's the outline of what's new: - Multiple plugin fixes (twitch, vaughlive, hitbox, etc.) - Donations! We've gone ahead and joined the Open Collective at https://opencollective.com/streamlink - Multiple doc updates - Support for SOCKS proxies - Code refactoring Many thanks to those who've contributed in this release! [Full changelog](https://github.com/streamlink/streamlink/compare/0.7.0...0.8.1) ## streamlink 0.7.0 (2017-06-30) 0.7.0 of Streamlink! Since our May release, we've incorporated quite a few changes! Outlined are the major features in this month's release: - Stream types will now be sorted accordingly in terms of quality - TeamLiquid.net Plugin added - Numerous plugin & bug fixes - Updated HomeBrew package - Improved CLI documentation Many thanks to those who've contributed in this release! If you think that this application is helpful, please consider supporting the maintainers by [donating](https://streamlink.github.io/donate.html). [Full changelog](https://github.com/streamlink/streamlink/compare/0.6.0...0.7.0) ## streamlink 0.6.0 (2017-05-11) Another release of Streamlink! We've updated more plugins, improved documentation, and moved out nightly builds to Bintray (S3 was costing *wayyyy* too much). Again, many thanks for those who've contributed! Thank you very much! [Full changelog](https://github.com/streamlink/streamlink/compare/0.5.0...0.6.0) ## streamlink 0.5.0 (2017-04-04) Streamlink 0.5.0! Lot's of contributions since the last release. As always, lot's of updating to plugins! One of the new features is the addition of Google Drive / Google Docs, you can now stream videos stored on Google Docs. We've also gone ahead and removed dead plugins (sites which have gone down) as well as added pycrypto as a dependency for future plugins. Again, many thanks for those who have contributed! Thank you very much! [Full changelog](https://github.com/streamlink/streamlink/compare/0.4.0...0.5.0) ## streamlink 0.4.0 (2017-03-09) 0.4.0 of Streamlink! 114 commits since the last release and *a lot* has changed. In general, we've added some localization as well as an assortment of new plugins. We've also introduced a change for Streamlink to *not* check for new updates each time Streamlink starts. We found this feature annoying as well as delaying the initial start of the stream. This feature can be re-enabled by the command line. The major features of this release are: - New plugins added - Ongoing support to current plugins via bug fixes - Ensure retries to HLS streams - Disable update check Many thanks to all contributors who have contributed in this release! [Full changelog](https://github.com/streamlink/streamlink/compare/0.3.2...0.4.0) ## streamlink 0.3.2 (2017-02-10) 0.3.2 release of Streamlink! A minor bug release of 0.3.2 to fix a few issues with stream providers. Thanks to all whom have contributed to this (tiny) release! [Full changelog](https://github.com/streamlink/streamlink/compare/0.3.1...0.3.2) ## streamlink 0.3.1 (2017-02-03) 0.3.1 release of Streamlink A *minor* release, we update our source code upload to *not* include the ffmpeg.exe binary as well as update a multitude of plugins. Thanks again for all the contributions as well as updates! [Full changelog](https://github.com/streamlink/streamlink/compare/0.3.0...0.3.1) ## streamlink 0.3.0 (2017-01-24) Release 0.3.0 of Streamlink! A lot of updates to each plugin (thank you @beardypig !), automated Windows releases, PEP8 formatting throughout Streamlink are some of the few updates to this release as we near a stable 1.0.0 release. Main features are: - Lot's of maintaining / updates to plugins - General bug and doc fixes - Major improvements to development (github issue templates, automatically created releases) [Full changelog](https://github.com/streamlink/streamlink/compare/0.2.0...0.3.0) ## streamlink 0.2.0 (2016-12-16) Release 0.2.0 of Streamlink! We've done numerous changes to plugins as well as fixed quite a few which were originally failing. Among these changes are updated docs as well as general UI/UX cleaning with console output. The main features are: - Additional plugins added - Plugin fixes - Cleaned up console output - Additional documentation (contribution, installation instructions) Again, thank you everyone whom contributed to this release! :D [Full changelog](https://github.com/streamlink/streamlink/compare/0.1.0...0.2.0) ## streamlink 0.1.0 (2016-11-21) A major update to Streamlink. With this release, we include a Windows binary as well as numerous plugin changes and fixes. The main features are: - Windows binary (and generation!) thanks to the fabulous work by @beardypig - Multiple plugin fixes - Remove unneeded run-as-root (no more warning you when you run as root, we trust that you know what you're doing) - Fix stream quality naming issue [Full changelog](https://github.com/streamlink/streamlink/compare/0.0.2...0.1.0) ## streamlink 0.0.2 (2016-10-12) The second ever release of Streamlink! In this release we've not only set the stepping stone for the further development of Streamlink (documentation site updated, CI builds working) but we're already fixing bugs and implementing features past the initial fork of livestreamer. The main features of this release are: - New windows build available and generated via pyinstaller - Multiple provider bug fixes (twitch, picarto, itvplayer, crunchyroll, periscope, douyutv) - Updated and reformed documentation which also includes our site https://streamlink.github.io As always, below is a `git shortlog` of all changes from the previous release of Streamlink (0.0.1) to now (0.0.2). [Full changelog](https://github.com/streamlink/streamlink/compare/0.0.1...0.0.2) ## streamlink 0.0.1 (2016-09-23) The first release of Streamlink! This is the first release from the initial fork of Livestreamer. We aim to have a concise, fast review process and progress in terms of development and future releases. Below is a `git shortlog` of all commits since the last change within Livestream (hash ab80dbd6560f6f9835865b2fc9f9c6015aee5658). This will serve as a base-point as we continue development of "Streamlink". New releases will include a list of changes as we add new features / code refactors to the existing code-base. [Full changelog](https://github.com/streamlink/streamlink/compare/ab80dbd...0.0.1) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745694559.9468186 streamlink-7.3.0/docs/cli/0000755000175100001660000000000015003227540014772 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/cli/config.rst0000644000175100001660000000722415003227510016773 0ustar00runnerdockerConfiguration file ================== Writing the command-line options every time is inconvenient, that's why Streamlink is capable of reading options from a configuration file instead. Location -------- Streamlink will look for config files in different locations depending on your platform: .. rst-class:: table-custom-layout table-custom-layout-platform-locations .. list-table:: :header-rows: 1 :width: 100% * - Platform - Location * - Linux, BSD - | **Path** | ``${XDG_CONFIG_HOME:-${HOME}/.config}/streamlink/config`` | **Example** | ``/home/USERNAME/.config/streamlink/config`` * - macOS - | **Path** | ``${HOME}/Library/Application Support/streamlink/config`` | **Example** | ``/Users/USERNAME/Library/Application Support/streamlink/config`` * - Windows - | **Path** | ``%APPDATA%\streamlink\config`` | **Example** | ``C:\Users\USERNAME\AppData\Roaming\streamlink\config`` You can also specify the location yourself using the :option:`--config` option. Loading config files can be suppressed using the :option:`--no-config` option. .. warning:: Streamlink's Windows installers automatically create a config file if it doesn't exist yet, but on any other platform or installation method, you must create the file yourself. .. note:: The ``XDG_CONFIG_HOME`` environment variable is part of the `XDG base directory specification`_ (`Arch Linux Wiki `_). The ``${VARIABLENAME:-DEFAULTVALUE}`` syntax is explained `here `_. .. _XDG base directory specification: https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html .. _xdg-base-dir-arch-wiki: https://wiki.archlinux.org/title/XDG_Base_Directory .. _Parameter expansion: https://www.gnu.org/software/bash/manual/bash.html#Shell-Parameter-Expansion Syntax ------ The config file is a simple text file and should contain one :ref:`command-line option ` (omitting the leading dashes) per line in the format:: option=value or for an option without value:: option .. warning:: Any quotes will be used as part of the argument value. Example ^^^^^^^ .. code-block:: bash # Player options player=mpv player-args=--cache 2048 player-no-close Plugin specific configuration file ---------------------------------- You may want to use specific options for some plugins only. This can be accomplished by setting these options in plugin-specific config files. Options defined in plugin-specific config files override options of the main config file when a URL matching the plugin is used. Streamlink expects these configs to be named like the main config but with ``.`` attached to the end. .. rst-class:: table-custom-layout table-custom-layout-platform-locations .. list-table:: :header-rows: 1 :width: 100% * - Platform - Location * - Linux, BSD - | **Path** | ``${XDG_CONFIG_HOME:-${HOME}/.config}/streamlink/config.pluginname`` | **Example** | ``/home/USERNAME/.config/streamlink/config.twitch`` * - macOS - | **Path** | ``${HOME}/Library/Application Support/streamlink/config.pluginname`` | **Example** | ``/Users/USERNAME/Library/Application Support/streamlink/config.twitch`` * - Windows - | **Path** | ``%APPDATA%\streamlink\config.pluginname`` | **Example** | ``C:\Users\USERNAME\AppData\Roaming\streamlink\config.twitch`` Have a look at the :ref:`list of plugins `, or check the :option:`--plugins` option to see the name of each built-in plugin. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/cli/metadata.rst0000644000175100001660000000615315003227510017306 0ustar00runnerdockerMetadata ======== Variables --------- Streamlink supports a number of metadata variables that can be used in the following CLI arguments: - :option:`--title` - :option:`--output` - :option:`--record` - :option:`--record-and-pipe` Metadata variables are surrounded by curly braces and can be escaped by doubling the curly brace characters, e.g. ``{variable}`` and ``{{not-a-variable}}``. .. warning:: The availability of each variable depends on the used plugin and whether that plugin supports this kind of metadata (:ref:`check the metadata availability of each plugin here `). Be aware that depending on the stream, certain metadata might not be available (yet), despite the plugin's implementation. If no metadata is available for a specific variable, then its substitution will either be a short placeholder text (:option:`--title`) or an empty string (:option:`--output`, :option:`--record`, :option:`--record-and-pipe`). **Conditional variables** .. list-table:: :header-rows: 1 :class: table-custom-layout table-custom-layout-platform-locations * - Variable - Description * - ``id`` - The unique ID of the stream, e.g. an internal numeric ID or randomized string. * - ``author`` - The stream's author, e.g. a channel or broadcaster name. * - ``category`` - The stream's category, e.g. the name of a game being played, a music genre, etc. * - ``game`` - Alias for ``category``. * - ``title`` - The stream's title, usually a short descriptive text. **Generic variables** .. list-table:: :header-rows: 1 :class: table-custom-layout table-custom-layout-platform-locations * - Variable - Description * - ``plugin`` - The name of the resolved plugin. See :ref:`Plugins ` for the name of each built-in plugin. * - ``url`` - The resolved URL of the stream. * - ``time`` - The current timestamp. Can optionally be formatted via ``{time:format}``. The format parameter string is passed to Python's `datetime.strftime()`_ method, so all the usual time directives are available. The default format is ``%Y-%m-%d_%H-%M-%S``. .. _datetime.strftime(): https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes Examples -------- .. code-block:: console $ streamlink --title "{author} - {category} - {title}" [STREAM] $ streamlink --output "~/recordings/{author}/{category}/{id}-{time:%Y%m%d%H%M%S}.ts" [STREAM] The :option:`--json` argument always lists the conditional metadata. .. code-block:: console $ streamlink --json twitch.tv/bobross | jq .metadata .. code-block:: json { "id": "49163597677", "author": "BobRoss", "category": "Art", "title": "A Happy Little Weekend Marathon!" } .. note:: Streamlink is not designed as a tool for general-purpose metadata retrieval. If your goal is to extract metadata from specific streaming sites, then it's usually a better idea to implement this metadata retrieval yourself using the available APIs of those sites or other means. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/cli/plugin-sideloading.rst0000644000175100001660000000557315003227510021311 0ustar00runnerdockerPlugin sideloading ================== Streamlink supports overriding its :ref:`built-in plugins ` or loading custom third party plugins without having to modify its sources or its built and installed `Python distribution`_. This is called plugin sideloading. Those additional plugin modules will automatically be loaded `from the paths listed below `_, or from the path(s) of the :option:`--plugin-dir` CLI argument, if it is set. .. attention:: **Do not** attempt to modify :ref:`built-in plugins ` or to add custom plugins to Streamlink's built `Python distribution`_. In order to keep loading times low, Streamlink implements a lazy plugin-loading system for its built-in plugins, which means that :func:`pluginmatcher ` and :func:`pluginargument ` data is pre-computed and cached, thus making modifications to plugin modules or adding new plugin modules pointless. Instead, sideload plugins. .. warning:: If one of the sideloaded plugins fails to load and execute, e.g. due to a ``SyntaxError`` being raised by the parser, then this exception won't be caught by Streamlink and the execution will stop, even if the input stream URL does not match the faulty plugin. .. note:: Custom plugin modules will always be loaded and executed at once, increasing the load and initialization time of the :class:`Streamlink session `. :ref:`Built-in plugins ` on the other hand are loaded lazily, unless overridden. .. _Python distribution: https://packaging.python.org/en/latest/glossary/#term-Built-Distribution Overriding ---------- If a plugin is added with the same name as a built-in plugin, then the added plugin will take precedence. This can be useful for upgrading or modifying plugins independently of the Streamlink version. In this case, a log message will be written to log level :option:`debug <--loglevel>`: .. code-block:: text [session][debug] Plugin PLUGINNAME is being overridden by PATH-TO-PLUGIN-FILE (sha256:CHECKSUM) Sideloading locations --------------------- .. rst-class:: table-custom-layout table-custom-layout-platform-locations .. list-table:: :header-rows: 1 :width: 100% * - Platform - Location * - Linux, BSD - | **Path** | ``${XDG_DATA_HOME:-${HOME}/.local/share}/streamlink/plugins`` | **Example** | ``/home/USERNAME/.local/share/streamlink/plugins`` * - macOS - | **Path** | ``${HOME}/Library/Application Support/streamlink/plugins`` | **Example** | ``/Users/USERNAME/Library/Application Support/streamlink/plugins`` * - Windows - | **Path** | ``%APPDATA%\streamlink\plugins`` | **Example** | ``C:\Users\USERNAME\AppData\Roaming\streamlink\plugins`` ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745694559.9468186 streamlink-7.3.0/docs/cli/plugins/0000755000175100001660000000000015003227540016453 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/cli/plugins/twitch.rst0000644000175100001660000002373215003227510020513 0ustar00runnerdockerTwitch ====== Authentication -------------- Official authentication support for Twitch via the ``--twitch-oauth-token`` and ``--twitch-oauth-authenticate`` CLI arguments had to be disabled in Streamlink's :ref:`1.3.0 release in November 2019 ` and both arguments were finally removed in the :ref:`2.0.0 release in December 2020 ` due to `restrictive changes`_ on Twitch's private REST API which prevented proper authentication flows from third party applications like Streamlink. The issue was that authentication data generated from third party applications could not be sent while acquiring streaming access tokens which are required for watching streams. Only authentication data generated by Twitch's website was accepted by the Twitch API. Later on in January 2021, Twitch moved the respective API endpoints to their GraphQL API which was already in use by their website for several years and shut down the old, private REST API. This means that authentication data, aka. the "OAuth token", needs to be read from the web browser after logging in on Twitch's website and it then needs to be set as a certain request header on these API endpoints. This unfortunately can't be automated easily by applications like Streamlink, so a new authentication feature was never implemented. **In order to get the personal OAuth token from Twitch's website which identifies your account**, open Twitch.tv in your web browser and after a successful login, open the developer tools by pressing :kbd:`F12` or :kbd:`CTRL+SHIFT+I`. Then navigate to the "Console" tab or its equivalent of your web browser and execute the following JavaScript snippet, which reads the value of the ``auth-token`` cookie, if it exists: .. code-block:: javascript document.cookie.split("; ").find(item=>item.startsWith("auth-token="))?.split("=")[1] Copy the resulting string consisting of 30 alphanumerical characters without any quotations. The final ``Authorization`` header which will identify your account while requesting a streaming access token can then be set via Streamlink's :option:`--http-header` or :option:`--twitch-api-header` CLI arguments. The former will set the header on any HTTP request made by Streamlink, even HLS Streams, while the latter will only do that on Twitch API requests, which is what should be done when authenticating and which is the reason why this CLI argument was added. The value of the ``Authorization`` header must be in the format of ``OAuth YOUR_TOKEN``. Notice the space character in the argument value, which requires quotation on command line shells: .. code-block:: console $ streamlink "--twitch-api-header=Authorization=OAuth abcdefghijklmnopqrstuvwxyz0123" twitch.tv/CHANNEL best The entire argument can optionally be added to Streamlink's (Twitch plugin specific) :ref:`config file `, which :ref:`doesn't require quotes `: .. code-block:: text twitch-api-header=Authorization=OAuth abcdefghijklmnopqrstuvwxyz0123 .. _restrictive changes: https://github.com/streamlink/streamlink/issues/2680#issuecomment-557605851 Embedded ads ------------ In 2019, Twitch has started sporadically embedding ads directly into streams in addition to their regular advertisement program on their website which can only overlay ads. The embedded ads situation has been an ongoing thing since then and has been turned off and on several times throughout the months and years, also with variations between regions, and it has recently been pushed more and more aggressively with long pre-roll ads. While this may be an annoyance for end-users who are used to using ad-blocker extensions in their web-browsers for blocking regular overlaying ads, applications like Streamlink face another problem, namely stream discontinuities when there's a transition between an ad and the regular stream content or another follow-up ad. Since Streamlink does only output a single progressive stream from reading Twitch's segmented HLS stream, ads can cause issues in certain players, as the output is not a cohesively encoded stream of audio and video data anymore during an ad transition. One of the problematic players is :ref:`VLC `, which is known to crash during these stream discontinuities in certain cases. Unfortunately, entirely preventing embedded ads is not possible unless a loophole on Twitch gets discovered which can be exploited. This has been the case a couple of times now and ad-workarounds have been implemented in Streamlink (see #3210) and various ad-blockers, but the solutions did only last for a couple of weeks or even days until Twitch patched these exploits. **To filter out ads and to prevent stream discontinuities in Streamlink's output**, the :option:`--twitch-disable-ads` argument was introduced in :ref:`Streamlink 1.1.0 in 2019 `, which filters out advertisement segments from Twitch's HLS streams and pauses the stream output until regular content becomes available again. The filtering logic has seen several iterations since then, with the latest big overhaul in :ref:`Streamlink 1.7.0 in 2020 `. **In addition to that**, special API request headers can be set via :option:`--twitch-api-header` or special API request parameters can be set via :option:`--twitch-access-token-param` that can prevent ads from being embedded into the stream, either :ref:`authentication data ` or other data discovered by the community. Client-integrity token ---------------------- In 2022, Twitch added client-integrity tokens to their web player when getting streaming access tokens. These client-integrity tokens are calculated using sophisticated JavaScript code which is infeasible to re-implement, as it not only involves obfuscated code that's much harder to reverse engineer and to extract data from, but also a custom JavaScript virtual machine implementation where bytecode gets interpreted which is encoded prior to that using randomization patterns. The interpreted bytecode performs various checks of the user's web browser and its features, and then determines whether the client is legit or not. The goal is to prevent bots and third party applications from accessing streams. Client-integrity tokens were treated as an optional request parameter when getting streaming access tokens. This changed on 2023-05-31 when Twitch made them a requirement, and it broke Streamlink's Twitch plugin (#5370). Since the only sensible solution for Streamlink to calculate client-integrity tokens was using a web browser, a new implementation was needed which could automate that. So in ``6.0.0`` the :ref:`streamlink.webbrowser ` API was implemented, which requires a Chromium-based web browser being installed on the user's system. See the :option:`--webbrowser` and related CLI arguments for more details. However, a couple of days after Twitch made these changes, they reverted the requirement again, but in order for Streamlink to be prepared for such requirements to be turned on again, the webbrowser API was added nevertheless. The decision was made to only use the webbrowser API when getting an access token fails, so launching a web browser unnecessarily could be avoided, even though it would run invisibly in "headless mode". Should client-integrity tokens be made a requirement again, then Streamlink will cache the generated token in the plugin cache after launching the web browser once. This cache can be cleared using the :option:`--twitch-purge-client-integrity` option. Low latency streaming --------------------- Low latency streaming on Twitch can be enabled by setting the :option:`--twitch-low-latency` argument and (optionally) configuring the :ref:`player ` via :option:`--player-args` and reducing its own buffer to a bare minimum. Setting :option:`--twitch-low-latency` will make Streamlink prefetch future HLS segments that are included in the HLS playlist and which can be requested ahead of time. As soon as content becomes available, Streamlink can download it without having to waste time on waiting for another HLS playlist refresh that might include new segments. In addition to that, :option:`--twitch-low-latency` also reduces :option:`--hls-live-edge` to a value of at most ``2``, and it also sets the :option:`--hls-segment-stream-data` argument. :option:`--hls-live-edge` defines how many HLS segments Streamlink should stay behind the stream's live edge, so that it can refresh playlists and download segments in time without causing buffering. Setting the value to ``1`` is not advised due to how prefetching works. :option:`--hls-segment-stream-data` lets Streamlink write the content of in-progress segment downloads to the output buffer instead waiting for the entire segment to complete first before data gets written. Since HLS segments on Twitch have a playback duration of 2 seconds for most streams, this further reduces output delay. .. note:: Low latency streams have to be enabled by the broadcasters on Twitch themselves. Regular streams can cause buffering issues with this option enabled due to the reduced :option:`--hls-live-edge` value. Unfortunately, there is no way to check whether a channel is streaming in low-latency mode before accessing the stream. Player buffer tweaks ^^^^^^^^^^^^^^^^^^^^ Since players do have their own input buffer, depending on how much data the player wants to keep in its buffer before it starts playing the stream, this can cause an unnecessary delay while trying to watch low latency streams. Player buffer sizes should therefore be tweaked via the :option:`--player-args` CLI argument or via the player's configuration options. The delay introduced by the player depends on the stream's bitrate and how much data is necessary to allow for a smooth playback without causing any stuttering, e.g. when running out out available data. Please refer to the player's own documentation for the available options. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/cli/plugins.rst0000644000175100001660000000011615003227510017200 0ustar00runnerdockerPlugin specific usage ===================== .. toctree:: plugins/twitch ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/cli/protocols.rst0000644000175100001660000000567315003227510017560 0ustar00runnerdockerStreaming protocols =================== There are many types of streaming protocols used by services today and Streamlink supports most of them. It's possible to tell Streamlink to access a streaming protocol directly instead of relying on a plugin to extract the streams from a URL for you. Playing built-in streaming protocols directly --------------------------------------------- A streaming protocol can be accessed directly by specifying it in the ``protocol://URL`` format with an optional list of parameters, like so: .. code-block:: console $ streamlink "protocol://https://streamingserver/path key1=value1 key2=value2" Depending on the input URL, the explicit protocol scheme may be omitted. The following example shows HLS streams (``.m3u8``) and DASH streams (``.mpd``): .. code-block:: console $ streamlink "https://streamingserver/playlist.m3u8" $ streamlink "https://streamingserver/manifest.mpd" Supported streaming protocols ----------------------------- .. list-table:: :header-rows: 1 :class: table-custom-layout * - Name - Explicit prefix * - Apple HTTP Live Streaming - ``hls://`` * - MPEG-DASH - ``dash://`` * - Progressive HTTP/HTTPS - ``httpstream://`` .. note:: Local files can be read by adding the ``file://`` scheme to the ``URL`` component. Protocol parameters ------------------- When passing parameters to the built-in streaming protocols, the values will either be treated as plain strings or they will be interpreted as Python literals: .. code-block:: console $ streamlink "httpstream://https://streamingserver/path method=POST params={'abc':123} json=['foo','bar','baz']" .. code-block:: python method="POST" params={"key": 123} json=["foo", "bar", "baz"] The parameters from the example above are used to make an HTTP ``POST`` request with ``abc=123`` added to the query string and ``["foo", "bar", "baz"]`` used as the content of the HTTP request's body (the serialized JSON data). Some parameters allow you to configure the behavior of the streaming protocol implementation directly: .. code-block:: console $ streamlink "hls://https://streamingserver/path start_offset=123 duration=321 force_restart=True" Available parameters -------------------- Parameters are passed to the following methods of their respective stream implementations: .. list-table:: :header-rows: 1 :class: table-custom-layout * - Protocol prefix - Method references * - ``httpstream://`` - - :py:meth:`streamlink.stream.HTTPStream` - :py:meth:`requests.Session.request` * - ``hls://`` - - :py:meth:`streamlink.stream.HLSStream.parse_variant_playlist` - :py:meth:`streamlink.stream.HLSStream` - :py:meth:`streamlink.stream.MuxedHLSStream` - :py:meth:`requests.Session.request` * - ``dash://`` - - :py:meth:`streamlink.stream.DASHStream.parse_manifest` - :py:meth:`requests.Session.request` ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/cli/proxy.rst0000644000175100001660000000212215003227510016677 0ustar00runnerdockerProxy Support ------------- You can use the :option:`--http-proxy` option to change the proxy server that Streamlink will use for HTTP and HTTPS requests. :option:`--http-proxy` sets the proxy for all HTTP and HTTPS requests, including WebSocket connections. If separate proxies for each protocol are required, they can be set using environment variables - see the `Requests Proxies Documentation`_. Both HTTP and SOCKS proxies are supported, as well as authentication in each of them. .. note:: When using a SOCKS proxy, the ``socks4`` and ``socks5`` schemes mean that DNS lookups are done locally, rather than on the proxy server. To have the proxy server perform the DNS lookups, the ``socks4a`` and ``socks5h`` schemes should be used instead. .. code-block:: console $ streamlink --http-proxy "http://address:port" $ streamlink --http-proxy "https://address:port" $ streamlink --http-proxy "socks4a://address:port" $ streamlink --http-proxy "socks5h://address:port" .. _Requests Proxies Documentation: https://requests.readthedocs.io/en/latest/user/advanced/#proxies ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/cli/tutorial.rst0000644000175100001660000001534215003227510017371 0ustar00runnerdockerTutorial ======== Introduction ------------ Streamlink is a command-line application, which means that the commands described here should be typed into a terminal, or to be more precise, into a command-line shell. On Windows, you have to open either the `Windows Terminal`_ (recommended), `PowerShell`_ or `Command Prompt`_ (discouraged). On macOS, open the `Terminal `_ app, and if you're on Linux or BSD, the terminal emulator depends on your desktop environment and its configuration. The way Streamlink works is that it's only a means to extract and transport the streams, and the playback is done by an external video player. Streamlink works best with `VLC`_ or `mpv`_, which are also cross-platform, but other players may be compatible too, see the :ref:`Players ` page for a complete overview. Getting started --------------- Now to get into actually using Streamlink, let's say you want to watch the stream located on ``https://www.twitch.tv/nasa``, you start off by telling Streamlink where to attempt to extract streams from. This is done by setting the URL as the first argument on the :command:`streamlink` command: .. code-block:: console $ streamlink twitch.tv/nasa [cli][info] Found matching plugin twitch for URL twitch.tv/nasa Available streams: audio_only, 160p (worst), 360p, 480p, 720p60, 1080p60 (best) .. note:: You don't need to include the protocol when dealing with HTTP(s) URLs, e.g. just ``twitch.tv/nasa`` is enough and quicker to type. .. caution:: Depending on the command-line shell in use, any kind of command argument like the input URL for example may need to get quoted or escaped. See the `Shell syntax`_ section down below. This command will tell Streamlink to attempt to extract streams from the URL specified via its :ref:`plugins system ` which is responsible for resolving streams from specific streaming services. If it's successful, Streamlink will print out a list of available streams to choose from. In addition to Streamlink's plugins system, direct stream URLs can be played via the :ref:`supported streaming protocols `, which also support playback of local files using the ``file://`` protocol. Relative file paths and absolute paths are supported. All path separators are ``/``, even on Windows. .. code-block:: console $ streamlink hls://file://C:/hls/playlist.m3u8 [cli][info] Found matching plugin stream for URL hls://file://C:/hls/playlist.m3u8 Available streams: 180p (worst), 272p, 408p, 554p, 818p, 1744p (best) To select a stream and start playback, simply add the stream name as a second argument to the :command:`streamlink` command: .. code-block:: console $ streamlink twitch.tv/nasa 1080p60 [cli][info] Found matching plugin twitch for URL twitch.tv/nasa [cli][info] Opening stream: 1080p60 (hls) [cli][info] Starting player: vlc The stream you chose should now be playing in the player. It's a common use case to just want to start the highest quality stream and not be bothered with what it's named. To do this, just specify ``best`` as the stream name and Streamlink will attempt to rank the streams and open the one of highest quality. You can also specify ``worst`` to get the lowest quality. Now that you have a basic grasp of how Streamlink works, you may want to look into customizing it to your own needs, such as: - Creating a :ref:`configuration file ` of options you want to use. - Setting up your player to cache some data before playing the stream to help avoiding buffering issues or reducing its default buffering values for being able to watch low-latency streams. .. _Command Prompt: https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/windows-commands .. _PowerShell: https://docs.microsoft.com/en-us/powershell/ .. _Windows Terminal: https://docs.microsoft.com/en-us/windows/terminal/get-started .. _macOS Terminal: https://support.apple.com/guide/terminal/welcome/mac .. _VLC: https://videolan.org/ .. _mpv: https://mpv.io/ Shell syntax ------------ Depending on your used command-line shell and how you've entered the command, input strings like the URL or other command arguments may need to get `escaped `_ or quoted, because command-line shells interpret and treat certain characters as special symbols which can alter the shell's behavior, like characters for substituting/expanding strings, delimiting commands, path/file globbing, etc. The most relevant characters (among others) for input URLs that can cause unexpected results are - ``&``, ``;`` (command delimiting) - ``$``, ``%`` (variable substitution) - ``?``, ``*`` (path globbing) The quoting and escaping behavior varies wildly between each shell and its configuration, so please take a look at your shell's documentation about all the details, if you're unsure. **Quoting and character escaping examples:** URL: ``https://example/path?a=$one&b=%two%&c=*three*;&`` .. tab-set:: .. tab-item:: POSIX compliant - `BASH manual `_ - `ZSH manual `_ .. code-block:: sh streamlink 'https://example/path?a=$one&b=%two%&c=*three*;&' streamlink "https://example/path?a=\$one&b=%two%&c=*three*;&" streamlink https://example/path?a=\$one\&b=%two%\&c=*three*\;\& .. tab-item:: FISH - `FISH language documentation `_ .. code-block:: fish streamlink 'https://example/path?a=$one&b=%two%&c=*three*;&' streamlink "https://example/path?a=\$one&b=%two%&c=*three*;&" streamlink https://example/path\?a=\$one&b=%two%&c=\*three\*\;\& .. tab-item:: PowerShell - `PowerShell language specification `_ .. code-block:: pwsh streamlink 'https://example/path?a=$one&b=%two%&c=*three*;&' streamlink "https://example/path?a=`$one&b=%two%&c=*three*;&" streamlink https://example/path?a=`$one`&b=%two%`&c=*three*`;`& .. tab-item:: Windows Batch - `Escape characters, delimiters and quotes `_ - `Percent sign escaping `_ .. code-block:: bat streamlink "https://example/path?a=$one&b="%"two"%"&c=*three*;&" streamlink https://example/path?a=$one^&b=^%two^%^&c=*three*^;^& .. _escape-character: https://en.wikipedia.org/wiki/Escape_character .. _bash: https://www.gnu.org/software/bash/manual/bash.html .. _zsh: https://zsh.sourceforge.io/Doc/Release/zsh_toc.html .. _fish: https://fishshell.com/docs/current/language.html .. _pwsh: https://learn.microsoft.com/en-us/powershell/scripting/lang-spec/chapter-02 .. _batch-ss64: https://ss64.com/nt/syntax-esc.html .. _batch-so: https://stackoverflow.com/a/31420292 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/cli.rst0000644000175100001660000000100215003227510015512 0ustar00runnerdockerCommand-Line Interface ====================== .. toctree:: :maxdepth: 3 :caption: Table of Contents Command-line usage Tutorial Configuration file Plugin sideloading Streaming protocols Proxy support Metadata Plugin specific usage Command-line usage ------------------ .. code-block:: console $ streamlink [OPTIONS] [STREAM] .. argparse:: ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/conf.py0000644000175100001660000001727615003227510015534 0ustar00runnerdocker#!/usr/bin/env python3 import os import sys from streamlink import __version__ as streamlink_version # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath('.')) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. needs_sphinx = '3.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.autosectionlabel', 'sphinx.ext.intersphinx', 'ext_html_template_vars', 'ext_argparse', 'ext_github', 'ext_plugins', 'ext_releaseref', 'myst_parser', 'sphinx_design', ] autosectionlabel_prefix_document = True # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = 'Streamlink' copyright = '2025, Streamlink' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = streamlink_version.split('+')[0] # The full version, including alpha/beta/rc tags. release = streamlink_version # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build', '_applications.rst'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. #pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] github_project = 'streamlink/streamlink' # -- Options for autodoc --------------------------------------------------- autodoc_default_options = { "show-inheritance": True, "members": True, "member-order": "groupwise", # autodoc_member_order "class-doc-from": "both", # autoclass_content } autodoc_inherit_docstrings = False autodoc_typehints = "description" # -- Options for intersphinx --------------------------------------------------- intersphinx_mapping = { # "python": ("https://docs.python.org/3", None), "requests": ("https://requests.readthedocs.io/en/stable/", None), } intersphinx_timeout = 60 # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'furo' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. html_theme_options = { "source_repository": "https://github.com/streamlink/streamlink/", "source_branch": "master", "source_directory": "docs/", "light_logo": "icon.svg", "dark_logo": "icon.svg", } # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # HTML template variables via the custom ext_html_template_vars extension html_template_vars = { "oneliner": ( "A command-line utility that extracts streams from various services and pipes them into a video player of choice." ), } # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. html_logo = "../icon.svg" # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] html_css_files = [ "styles/custom.css", "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.3.0/css/fontawesome.min.css", "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.3.0/css/solid.min.css", "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.3.0/css/brands.min.css", ] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # Custom sidebar templates, maps document names to template names. html_sidebars = { '**': [ 'sidebar/scroll-start.html', 'sidebar/brand.html', 'sidebar/search.html', 'sidebar/navigation.html', 'sidebar/github-buttons.html', 'sidebar/scroll-end.html', ] } # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. html_domain_indices = False # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. html_show_sourcelink = False # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'streamlinkdoc' # -- Options for manual page output -------------------------------------------- # Only include the man page in builds with the "man" tag set: via `-t man` (see Makefile) if "man" not in tags: # type: ignore[name-defined] exclude_patterns.append("_man.rst") # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ( "_man", "streamlink", "extracts streams from various services and pipes them into a video player of choice", ["Streamlink Contributors"], 1, ), ] # If true, show URL addresses after external links. #man_show_urls = False # If true, make a section directory on build man page. # Always set this to false to fix inconsistencies between recent sphinx releases man_make_section_directory = False ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/deprecations.rst0000644000175100001660000003126315003227510017437 0ustar00runnerdockerDeprecations ============ streamlink 7.0.0 ---------------- --verbose-player ^^^^^^^^^^^^^^^^ The ``--verbose-player`` CLI argument has been deprecated in favor of :option:`--player-verbose`. --fifo ^^^^^^ The ``--fifo`` CLI argument has been deprecated in favor of :option:`--player-fifo`. streamlink 6.11.0 ----------------- -R/--record-and-pipe ^^^^^^^^^^^^^^^^^^^^ The ``-R``/``--record-and-pipe`` CLI argument has been deprecated in favor of using both :option:`--stdout` and :option:`--record` arguments at the same time. streamlink 6.8.0 ---------------- streamlink.plugins re-exports ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :bdg-ref-danger:`Removed in 7.0.0 ` Importing :class:`NoPluginError `, :class:`NoStreamsError `, :class:`PluginError `, or :class:`Plugin ` from ``streamlink.plugins`` now emits a :exc:`StreamlinkDeprecationWarning`. These re-exports have already been deprecated for more than ten years and will finally be removed in the next major version. Import from the correct paths instead. streamlink 6.7.0 ---------------- --plugin-dirs ^^^^^^^^^^^^^ The ``--plugin-dirs`` CLI argument for sideloading plugins from one or more custom directories, separated by commas, has been deprecated in favor of the newly added :option:`--plugin-dir` CLI argument, which only takes a single path, but can be set multiple times for loading plugins from more than one path. streamlink 6.6.0 ---------------- HTTPSession and HTTPAdapters ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :bdg-ref-danger:`Removed in 7.0.0 ` The module structure of the :class:`Streamlink ` session implementation and related classes like the ``HTTPSession`` has been re-organized. Importing ``HTTPSession`` from either ``streamlink.plugin.api`` or ``streamlink.plugin.api.http_session`` is now deprecated. It was never intended to import this class directly. Plugin implementors should always reference the session's ``HTTPSession`` instance via the :attr:`Streamlink.http ` attribute. In addition, importing ``TLSNoDHAdapter`` or ``TLSSecLevel1Adapter`` from ``streamlink.plugin.api.http_session`` is now also deprecated. Import from the ``streamlink.session.http`` module instead, if actually necessary. Streamlink.{get,load}_plugins() ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ As a result of the code refactoring mentioned above, the following plugins-related methods on the :class:`Streamlink ` session class have been deprecated: ``Streamlink.get_plugins()`` has been deprecated in favor of :meth:`Streamlink.plugins.get_loaded() `. ``Streamlink.load_plugins(path)`` has been deprecated in favor of :meth:`Streamlink.plugins.load_path(path) `. ``Streamlink.load_builtin_plugins()`` has been deprecated in favor of using the :class:`plugins_builtin ` Streamlink session keyword argument. The old method was never publicly documented and was only used internally upon initialization. streamlink 5.4.0 ---------------- --force-progress ^^^^^^^^^^^^^^^^ :bdg-ref-danger:`Removed in 7.0.0 ` The ``--force-progress`` CLI argument has been deprecated in favor of :option:`--progress=force`. streamlink 5.3.0 ---------------- Global plugin arguments ^^^^^^^^^^^^^^^^^^^^^^^ :bdg-ref-danger:`Removed in 6.0.0 ` The ``is_global=True`` :py:class:`plugin argument ` parameter has been deprecated. Instead of defining a global plugin argument to set a key-value pair on the plugin's options, use the respective option on the plugin's Streamlink session instance instead. streamlink 5.2.0 ---------------- plugin.api.validate.text ^^^^^^^^^^^^^^^^^^^^^^^^ :bdg-ref-danger:`Removed in 6.0.0 ` The ``plugin.api.validate.text`` alias for ``str`` has been marked as deprecated, as it is a remnant of the py2 implementation. Simply replace ``validate.text`` with ``str`` in each validation schema. streamlink 5.0.0 ---------------- Plugin.__init__(self, url) compatibility wrapper ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :bdg-ref-danger:`Removed in 6.0.0 ` With the removal of the ``Plugin.bind()`` class method which was used for setting up the :py:class:`Streamlink ` session instance and module name in each plugin class, the :py:class:`Plugin ` constructor's signature was changed and it now requires the ``session`` and ``url`` arguments. Implementors of custom plugins should define variable positional arguments and keyword arguments when subclassing and adding a custom constructor (``*args, **kwargs``), and the ``url`` should be accessed via ``self.url`` after calling the constructor of the super class. Compatibility wrappers were added for old custom plugin implementations, and a deprecation message will be shown until the compatibility wrappers will get removed in a future release. streamlink 4.2.0 ---------------- url_master in HLSStream ^^^^^^^^^^^^^^^^^^^^^^^ The ``url_master`` parameter and attribute of the :py:class:`HLSStream ` and :py:class:`MuxedHLSStream ` classes have been deprecated in favor of the ``multivariant`` parameter and attribute. ``multivariant`` is an :py:class:`M3U8` reference of the parsed HLS multivariant playlist. streamlink 3.0.0 ---------------- https-proxy option ^^^^^^^^^^^^^^^^^^ :ref:`HTTPS proxy CLI option ` and the respective :ref:`Session options ` have been deprecated in favor of a single :option:`--http-proxy` that sets the proxy for all HTTP and HTTPS requests, including WebSocket connections. streamlink 2.4.0 ---------------- Stream-type related CLI arguments ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :bdg-ref-danger:`Removed in 7.0.0 ` :ref:`Stream-type related CLI arguments ` and the respective :ref:`Session options ` have been deprecated in favor of existing generic arguments/options, to avoid redundancy and potential confusion. - use :option:`--stream-segment-attempts` instead of ``--{dash,hds,hls}-segment-attempts`` - use :option:`--stream-segment-threads` instead of ``--{dash,hds,hls}-segment-threads`` - use :option:`--stream-segment-timeout` instead of ``--{dash,hds,hls}-segment-timeout`` - use :option:`--stream-timeout` instead of ``--{dash,hds,hls,rtmp,http-stream}-timeout`` streamlink 2.3.0 ---------------- Plugin.can_handle_url() and Plugin.priority() ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :bdg-ref-danger:`Removed in 6.0.0 ` A new plugin URL matching API was introduced in 2.3.0 which will help Streamlink with static code analysis and an improved plugin loading mechanism in the future. Plugins now define their matching URLs and priorities declaratively. The old ``can_handle_url`` and ``priority`` classmethods have therefore been deprecated and will be removed in the future. When side-loading plugins which don't implement the new ``@pluginmatcher`` but implement the old classmethods, a deprecation message will be written to the info log output for the first plugin that gets resolved this way. **Deprecated plugin URL matching** .. code-block:: python import re from streamlink.plugin import Plugin from streamlink.plugin.plugin import HIGH_PRIORITY, NORMAL_PRIORITY class MyPlugin(Plugin): _re_url_one = re.compile( r"https?://pattern-(?Pone)" ) _re_url_two = re.compile(r""" https?://pattern-(?Ptwo) """, re.VERBOSE) @classmethod def can_handle_url(cls, url: str) -> bool: return cls._re_url_one.match(url) is not None \ or cls._re_url_two.match(url) is not None @classmethod def priority(cls, url: str) -> int: if cls._re_url_two.match(url) is not None: return HIGH_PRIORITY else: return NORMAL_PRIORITY def _get_streams(self): match_one = self._re_url_one.match(self.url) match_two = self._re_url_two.match(self.url) match = match_one or match_two param = match.group("param") if match_one: yield ... elif match_two: yield ... __plugin__ = MyPlugin **Migration** .. code-block:: python import re from streamlink.plugin import HIGH_PRIORITY, Plugin, pluginmatcher @pluginmatcher(re.compile( r"https?://pattern-(?Pone)" )) @pluginmatcher(priority=HIGH_PRIORITY, pattern=re.compile(r""" https?://pattern-(?Ptwo) """, re.VERBOSE)) class MyPlugin(Plugin): def _get_streams(self): param = self.match.group("param") if self.matches[0]: yield ... elif self.matches[1]: yield ... __plugin__ = MyPlugin .. note:: Plugins which have more sophisticated logic in their ``can_handle_url()`` classmethod need to be rewritten with multiple ``@pluginmatcher`` decorators and/or an improved ``_get_streams()`` method which returns ``None`` or raises a ``NoStreamsError`` when there are no streams to be found on that particular URL. streamlink 2.2.0 ---------------- Config file paths ^^^^^^^^^^^^^^^^^ :bdg-ref-danger:`Removed in 7.0.0 ` Streamlink's default config file paths got updated and corrected on Linux/BSD, macOS and Windows. Old and deprecated paths will be dropped in the future. Only the first existing config file will be loaded. If a config file gets loaded from a deprecated path, a deprecation message will be written to the info log output. To resolve this, move the config file(s) to the correct location or copy the contents of the old file(s) to the new one(s). .. note:: Please note that this also affects all plugin config files, as they use the same path as the primary config file but with ``.pluginname`` appended to the file name, eg. ``config.twitch``. .. warning:: **On Windows**, when installing Streamlink via the Windows installer, a default config file gets created automatically due to technical reasons (bundled ffmpeg and rtmpdump dependencies). This means that the Windows installer will create a config file with the new name when upgrading from an earlier version to Streamlink 2.2.0+, and the old config file won't be loaded as a result of this. This is unfortunately a soft breaking change, as the Windows installer is not supposed to touch user config data and the users are required to update this by themselves. **Deprecated paths** .. list-table:: :header-rows: 1 :class: table-custom-layout table-custom-layout-platform-locations * - Platform - Location * - Linux/BSD - - ``${HOME}/.streamlinkrc`` * - macOS - - ``${XDG_CONFIG_HOME:-${HOME}/.config}/streamlink/config`` - ``${HOME}/.streamlinkrc`` * - Windows - - ``%APPDATA%\streamlink\streamlinkrc`` **Migration** .. list-table:: :header-rows: 1 :class: table-custom-layout table-custom-layout-platform-locations * - Platform - Location * - Linux/BSD - ``${XDG_CONFIG_HOME:-${HOME}/.config}/streamlink/config`` * - macOS - ``${HOME}/Library/Application Support/streamlink/config`` * - Windows - ``%APPDATA%\streamlink\config`` Custom plugins sideloading paths ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :bdg-ref-danger:`Removed in 7.0.0 ` Streamlink's default custom plugins directory path got updated and corrected on Linux/BSD and macOS. Old and deprecated paths will be dropped in the future. **Deprecated paths** .. list-table:: :header-rows: 1 :class: table-custom-layout table-custom-layout-platform-locations * - Platform - Location * - Linux/BSD - ``${XDG_CONFIG_HOME:-${HOME}/.config}/streamlink/plugins`` * - macOS - ``${XDG_CONFIG_HOME:-${HOME}/.config}/streamlink/plugins`` **Migration** .. list-table:: :header-rows: 1 :class: table-custom-layout table-custom-layout-platform-locations * - Platform - Location * - Linux/BSD - ``${XDG_DATA_HOME:-${HOME}/.local/share}/streamlink/plugins`` * - macOS - ``${HOME}/Library/Application Support/streamlink/plugins`` ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/developing.rst0000644000175100001660000002165215003227510017114 0ustar00runnerdockerDeveloping ========== Setup ----- Setting up the repository ^^^^^^^^^^^^^^^^^^^^^^^^^ In order to start working on Streamlink, you must first install the latest stable version of ``git``, optionally fork the repository on Github onto your account if you want to submit changes in a pull request, and then locally clone the repository. .. code-block:: bash mkdir streamlink cd streamlink git clone --origin=upstream 'https://github.com/streamlink/streamlink.git' . git remote add fork 'git@github.com:/streamlink.git' git remote -v git fetch --all When submitting a pull request, commit and push your changes onto a different branch. .. code-block:: bash git checkout master git pull upstream master git checkout -b new/feature/or/bugfix/branch git add ./foo git commit git push fork new/feature/or/bugfix/branch Setting up a new environment ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ While working on any kind of python-based project, it is usually best to do this in a virtual environment which is isolated from the Python environment of the host system. This ensures that development can be done in a clean space which is free of version conflicts and other unrelated packages. First, make sure that you have the latest stable versions of Python and `pip`_ installed: .. code-block:: bash python --version pip --version Then set up a new virtual environment using `venv`_ of the Python standard library: .. code-block:: bash # replace ~/venvs/streamlink with your path of choice and give it a proper name python -m venv ~/venvs/streamlink Now activate the virtual environment by sourcing the activation shell script: .. code-block:: bash source ~/venvs/streamlink/bin/activate # non-POSIX shells have their own activation script, e.g. FISH source ~/venvs/streamlink/bin/activate.fish .. code-block:: pwsh # on Windows, activation scripts are located in the Scripts/ subdirectory instead of bin/ ~\venvs\streamlink\Scripts\Activate.ps1 .. _pip: https://pip.pypa.io/en/stable/ .. _venv: https://docs.python.org/3/library/venv.html Installing Streamlink ^^^^^^^^^^^^^^^^^^^^^ After activating the new virtual environment, Streamlink's development dependencies and Streamlink itself need to be installed. Regular development dependencies and documentation related dependencies are listed in the text files shown below and need to be installed separately. .. code-block:: bash # install additional dependencies pip install -U -r dev-requirements.txt pip install -U -r docs-requirements.txt # install Streamlink in "editable" mode pip install -e . # validate that Streamlink is working streamlink --loglevel=debug Validating changes ------------------ Before submitting a pull request, run tests, perform code linting and build the documentation on your system first, to see if your changes contain any mistakes or errors. This will be done automatically for each pull request on each change, but performing these checks locally avoids unnecessary build failures. .. code-block:: bash # run automated tests pytest # or just run a subset of all tests pytest path/to/test-file.py::TestClassName::test_method_name ... # check code for linting errors ruff check . # check code for formatting errors ruff format --diff . # check code for typing errors mypy # build the documentation make --directory=docs clean html # check the documentation python -m http.server 8000 --bind '127.0.0.1' --directory 'docs/_build/html/' "${BROWSER}" http://127.0.0.1:8000/ Code style ---------- Streamlink uses `Ruff`_ as primary code `linting `_ and `formatting `_ tool. The project aims to use best practices for achieving great code readability with minimal git diffs, as detailed in :pep:`8` and implemented in related linting and formatting tools, such as `Black`_. For detailed linting and formatting configurations specific to Streamlink, please have a look at `pyproject.toml`_. It might be helpful to new plugin authors to pick a small and recently modified existing plugin to use as an initial template from which to work. If care is taken to preserve existing blank lines during modification, the main plugin structure should be compliant-ready for `linting `_. .. _Ruff: https://github.com/astral-sh/ruff .. _Ruff-linter: https://docs.astral.sh/ruff/linter/ .. _Ruff-formatter: https://docs.astral.sh/ruff/formatter/ .. _Black: https://black.readthedocs.io/en/stable/the_black_code_style/current_style.html .. _pyproject.toml: https://github.com/streamlink/streamlink/blob/master/pyproject.toml Plugins ------- Adding plugins ^^^^^^^^^^^^^^ 1. Implement the plugin in ``src/streamlink/plugins/pluginname.py``, similar to already existing plugins. Check the git log for recently added or modified plugins to help you get an overview of what's needed to properly implement a plugin. A complete guide is currently not available. Each plugin class requires at least one ``pluginmatcher`` decorator which defines the URL regex, matching priority and an optional name. Plugins need to implement the :meth:`_get_streams() ` method which must return ``Mapping[str,Stream] | Iterable[Tuple[str,Stream]] | Iterator[Tuple[str,Stream]] | None``. ``Stream`` is the base class of :class:`HTTPStream `, :class:`HLSStream ` and :class:`DASHStream `. Plugins also require metadata which will be read when building the documentation. This metadata contains information about the plugin, e.g. which URLs it accepts, which kind of streams it returns, whether content is region-locked, or if any kind of account or subscription is needed for watching the content, etc. This metadata needs to be set as a header comment at the beginning of the plugin file, in the following format (order of items is important): .. code-block:: python """ $description A brief description of the website, streaming service, etc. $url A URL which matches the plugin. No http:// or https:// scheme prefixes allowed. $url Multiple URLs can be set. Duplicates are not allowed. $type The type of content. Needs to be either "live", "vod", or "live, vod", without quotes. $region A comma-separated list of countries if region-lock applies. (optional) $account A brief note about account or subscription requirements. (optional) $notes Further short notes that may be useful. (optional) """ 2. Add at least tests for the URL regex matching in ``tests/plugins/test_pluginname.py``. To do so, import the ``PluginCanHandleUrl`` test base class from ``tests.plugins``, subclass it with a proper name, add the ``__plugin__`` class attribute and add all URLs required for testing the plugin matchers to the ``should_match`` list. The optional ``should_not_match`` negative matching list should only contain URLs which the plugin should actively not match, which means generic negative-matches are not allowed here, as they will already get added by the plugin test configuration. In addition to the positive matching list, ``should_match_groups`` is an optional list for testing capture groups values for given URL inputs. It's a list of tuples where the first tuple item is a URL and the second item either a dictionary of regex capture group names and values (excluding ``None`` values), or a tuple of unnamed capture group values. URLs from the ``should_match_groups`` list automatically get added to ``should_match`` and don't need to be added twice. If the plugin defines named matchers, then URLs in the test fixtures must be tuples of the matcher name and the URL itself. Unnamed matchers must not match named URL test fixtures and vice versa. Every plugin matcher must have at least one URL test fixture that matches. .. code-block:: python from streamlink.plugins.pluginfile import MyPluginClassName from tests.plugins import PluginCanHandleUrl class TestPluginCanHandleUrlMyPluginClassName(PluginCanHandleUrl): __plugin__ = MyPluginClassName should_match = [ "https://host/path/one", ("specific-path-matcher", "https://host/path/two"), ] should_match_groups = [ ("https://host/stream/123", {"stream": "123"}), ("https://host/stream/456/foo", ("456", "foo")), (("user-matcher", "https://host/user/one"), {"user": "one"}), (("user-matcher", "https://host/user/two"), ("two", None)), (("user-matcher", "https://host/user/two/foo"), ("two", "foo")), ] should_not_match = [ "https://host/path/three", ] Removing plugins ^^^^^^^^^^^^^^^^ 1. Remove the plugin file from ``src/streamlink/plugins/`` and the test file from ``tests/plugins/`` ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/docutils.conf0000644000175100001660000000005315003227510016713 0ustar00runnerdocker[restructuredtext parser] smart_quotes=no ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/ext_argparse.py0000644000175100001660000001456415003227510017270 0ustar00runnerdocker""" Convert an argparse parser to option directives. Inspired by sphinxcontrib.autoprogram but with a few differences: - Contains some simple pre-processing on the help messages to make the Sphinx version a bit prettier. """ import argparse import re from importlib import import_module from textwrap import dedent from docutils import nodes from docutils.parsers.rst import Directive from docutils.parsers.rst.directives import unchanged from docutils.statemachine import ViewList from sphinx.errors import ExtensionError from sphinx.util.nodes import nested_parse_with_titles _block_re = re.compile(r":\n{2}\s{2}") _default_re = re.compile(r"Default is (.+)\.\n") _note_re = re.compile(r"Note: (.*?)(?:\n\n|\n*$)", re.DOTALL) _option_line_re = re.compile(r"^(?!\s{2,}%\(prog\)s|\s{2,}--\w[\w-]*\w\b|Example: )(.+)$", re.MULTILINE) _option_re = re.compile(r"(?:^|(?<=\s))(?P--\w[\w-]*\w)(?P=\w+)?\b") _prog_re = re.compile(r"%\(prog\)s") _percent_re = re.compile(r"%%") _inline_code_block_re = re.compile(r"(? argparse.ArgumentParser: try: mod = import_module(module) obj = getattr(mod, attr) except Exception as err: raise ExtensionError("Invalid ext_argparse module or attr value") from err return obj() if callable(obj) else obj def process_help(self, helptext): # Dedent the help to make sure we are always dealing with # non-indented text. helptext = dedent(helptext) helptext = _inline_code_block_re.sub( lambda m: ":code:`{0}`".format(m.group(1).replace("\\", "\\\\")), helptext, ) helptext = _example_inline_code_block_re.sub(r":code:`\1`", helptext) # Replace option references with links. # Do this before indenting blocks and notes. helptext = _option_line_re.sub( lambda m: _option_re.sub( lambda m2: f":option:`{m2['arg']}{m2['val'] or ''}`" if m2["arg"] in self._available_options else m2[0], m[1], ), helptext, ) # Create simple blocks. helptext = _block_re.sub("::\n\n ", helptext) # Boldify the default value. helptext = _default_re.sub(r"Default is: **\1**.\n", helptext) # Create note directives from "Note: " paragraphs. helptext = _note_re.sub( lambda m: ".. note::\n\n" + indent(m.group(1)) + "\n\n", helptext, ) # workaround to replace %(prog)s with streamlink helptext = _prog_re.sub("streamlink", helptext) # fix escaped chars for percent-formatted argparse help strings helptext = _percent_re.sub("%", helptext) # create cross-links for the "Metadata variables" and "Plugins" sections helptext = re.sub( r"the \"Metadata variables\" section", 'the ":ref:`Metadata variables `" section', helptext, ) helptext = re.sub( r"the \"Plugins\" section", 'the ":ref:`Plugins `" section', helptext, ) return indent(helptext) def generate_group_rst(self, group): for action in group._group_actions: # don't document suppressed parameters if action.help == argparse.SUPPRESS: continue metavar = action.metavar if isinstance(metavar, tuple): metavar = " ".join(metavar) options = [] # parameter(s) with metavar if action.option_strings and metavar: for arg in action.option_strings: # optional parameter value if action.nargs == "?": metavar = f"[{metavar}]" options.append(f"{arg} {metavar}") # positional parameter elif metavar: options.append(metavar) # parameter(s) without metavar else: options += action.option_strings directive = ".. option:: " options = f"\n{' ' * len(directive)}".join(options) yield f"{directive}{options}" yield "" for line in self.process_help(action.help).split("\n"): yield line yield "" def generate_parser_rst(self, parser, parent=None, depth=0): if depth >= len(self._headlines): return for group in parser.NESTED_ARGUMENT_GROUPS[parent]: is_parent = group in parser.NESTED_ARGUMENT_GROUPS # Exclude empty groups if not is_parent and not any(action.help != argparse.SUPPRESS for action in group._group_actions or []): continue title = group.title yield "" yield title yield self._headlines[depth] * len(title) yield from self.generate_group_rst(group) if is_parent: yield "" yield from self.generate_parser_rst(parser, group, depth + 1) def run(self): module = self.options.get("module", self._DEFAULT_MODULE) attr = self.options.get("attr", self._DEFAULT_ATTR) parser = self.get_parser(module, attr) for action in parser._actions: # positional parameters have an empty option_strings list self._available_options += action.option_strings or [action.dest] node = nodes.section() node.document = self.state.document result = ViewList() for line in self.generate_parser_rst(parser): result.append(line, "argparse") nested_parse_with_titles(self.state, result, node) return node.children def setup(app): app.add_directive("argparse", ArgparseDirective) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/ext_github.py0000644000175100001660000000404315003227510016735 0ustar00runnerdocker"""Creates Github links from @user and #issue text. Bascially a much simplified version of sphinxcontrib.issuetracker with support for @user. """ import re from docutils import nodes from docutils.transforms import Transform GITHUB_ISSUE_URL = "https://github.com/{0}/issues/{1}" GITHUB_USER_URL = "https://github.com/{1}" class GithubReferences(Transform): default_priority = 999 def apply(self): config = self.document.settings.env.config issue_re = re.compile(config.github_issue_pattern) mention_re = re.compile(config.github_mention_pattern) self._replace_pattern(issue_re, GITHUB_ISSUE_URL) self._replace_pattern(mention_re, GITHUB_USER_URL) def _replace_pattern(self, pattern, url_format): project = self.document.settings.env.config.github_project for node in self.document.traverse(nodes.Text): parent = node.parent if isinstance(parent, (nodes.reference, nodes.literal, nodes.FixedTextElement)): continue text = str(node) new_nodes = [] last_ref_end = 0 for match in pattern.finditer(text): head = text[last_ref_end : match.start()] if head: new_nodes.append(nodes.Text(head)) last_ref_end = match.end() ref = url_format.format(project, match.group(1)) link = nodes.reference( match.group(0), match.group(0), refuri=ref, ) new_nodes.append(link) if not new_nodes: continue tail = text[last_ref_end:] if tail: new_nodes.append(nodes.Text(tail)) parent.replace(node, new_nodes) def setup(app): app.add_config_value("github_project", None, "env") app.add_config_value("github_issue_pattern", r"#(\d+)", "env") app.add_config_value("github_mention_pattern", r"@([\w-]+)", "env") app.add_transform(GithubReferences) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/ext_html_template_vars.py0000644000175100001660000000102215003227510021337 0ustar00runnerdockerfrom __future__ import annotations from typing import Any from sphinx.addnodes import document from sphinx.application import Sphinx _CONFIG_VAR = "html_template_vars" def update_context( app: Sphinx, pagename: str, templatename: str, context: dict[str, Any], doctree: document, ) -> None: for k, v in getattr(app.config, _CONFIG_VAR).items(): context[k] = v def setup(app: Sphinx) -> None: app.add_config_value(_CONFIG_VAR, {}, "") app.connect("html-page-context", update_context) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/ext_plugins.py0000644000175100001660000002123015003227510017131 0ustar00runnerdockerfrom __future__ import annotations import abc import ast import pkgutil import re import tokenize from collections.abc import Iterator from pathlib import Path from typing import Any from docutils import nodes from docutils.parsers.rst import Directive from docutils.statemachine import ViewList from sphinx.errors import ExtensionError from sphinx.util.nodes import nested_parse_with_titles from streamlink import plugins as streamlink_plugins class IDatalistItem(abc.ABC): @abc.abstractmethod def generate(self) -> Iterator[str]: raise NotImplementedError class IMetadataItem(IDatalistItem): @abc.abstractmethod def set(self, value: Any) -> None: raise NotImplementedError class MetadataItem(IMetadataItem): def __init__(self, title: str): self.title = title self.value: str | None = None def set(self, value: str) -> None: self.value = value def generate(self) -> Iterator[str]: if self.value is None: return yield f":{self.title}: {self.value}" class MetadataList(IMetadataItem): def __init__(self, title: str): self.title = title self.value: list[str] = [] def set(self, value: str) -> None: self.value.append(value) def generate(self) -> Iterator[str]: if not self.value: return yield f":{self.title}: - {' '.join(self.get_item(0))}" indent = " " * len(f":{self.title}:") for idx in range(1, len(self.value)): yield f"{indent} - {' '.join(self.get_item(idx))}" def get_item(self, idx: int) -> Iterator[str]: yield self.value[idx] class MetadataWebbrowserItem(MetadataItem): def __init__(self): super().__init__("Web browser") def generate(self) -> Iterator[str]: if self.value is None: return yield from super().generate() yield " [:ref:`? `]" class MetadataMetadataList(MetadataList): def __init__(self): super().__init__("Metadata") def get_item(self, idx: int) -> Iterator[str]: variable, *data = str(self.value[idx]).split(" ") yield f":ref:`{variable} `" if data: yield " ".join(["-", *data]) class PluginOnGithub(IDatalistItem): url_source = "https://github.com/streamlink/streamlink/blob/master/src/streamlink/plugins/{name}.py" url_issues = "https://github.com/streamlink/streamlink/issues?q=is%3Aissue+sort%3Aupdated-desc+plugins.{name}" def __init__(self, pluginname: str): self.pluginname = pluginname def generate(self) -> Iterator[str]: source = f"`Source <{self.url_source.format(name=self.pluginname)}>`__" issues = f"`Issues <{self.url_issues.format(name=self.pluginname)}>`__" yield f":GitHub: {source}, {issues}" class PluginArguments(ast.NodeVisitor, IDatalistItem): def __init__(self, pluginname, pluginast): super().__init__() self.pluginname = pluginname self.arguments = [] self.visit(pluginast) def generate(self) -> Iterator[str]: if not self.arguments: return indent = " " * len(":Arguments: ") yield f":Arguments: - :option:`--{self.arguments[0]}`" for arg in self.arguments[1:]: yield f"{indent} - :option:`--{arg}`" def visit_ClassDef(self, node: ast.ClassDef) -> None: for base in node.bases: if not isinstance(base, ast.Name): continue if base.id == "Plugin": break else: return for decorator in node.decorator_list: if ( not isinstance(decorator, ast.Call) or not isinstance(decorator.func, ast.Name) or decorator.func.id != "pluginargument" or (len(decorator.args) == 0 and len(decorator.keywords) == 0) ): continue if any( True for kw in decorator.keywords if ( kw.arg == "help" and type(kw.value) is ast.Attribute and kw.value.attr == "SUPPRESS" and type(kw.value.value) is ast.Name and kw.value.value.id == "argparse" ) ): continue custom_name = next( (kw.value.value for kw in decorator.keywords if kw.arg == "argument_name" and type(kw.value) is ast.Constant), None, ) if custom_name: self.arguments.append(custom_name) continue name = next( (kw.value.value for kw in decorator.keywords if kw.arg == "name" and type(kw.value) is ast.Constant), None, ) or ( decorator.args and type(decorator.args[0]) is ast.Constant and decorator.args[0].value ) # fmt: skip if name: self.arguments.append(f"{self.pluginname}-{name}") class PluginMetadata: def __init__(self, name: str, pluginast): self.name: str = name self.items: dict[str, IMetadataItem] = dict( description=MetadataItem("Description"), url=MetadataList("URL(s)"), type=MetadataItem("Type"), webbrowser=MetadataWebbrowserItem(), metadata=MetadataMetadataList(), region=MetadataItem("Region"), account=MetadataItem("Account"), notes=MetadataList("Notes"), ) self.additional: list[IDatalistItem] = [ PluginArguments(name, pluginast), PluginOnGithub(name), ] def set(self, key: str, value: str) -> None: if key not in self.items: raise KeyError(f"Invalid plugin metadata key '{key}' in plugin '{self.name}'") self.items[key].set(value) def generate(self) -> Iterator[str]: yield self.name yield "-" * len(self.name) yield "" for metadata in self.items.values(): yield from metadata.generate() for item in self.additional: yield from item.generate() yield "" yield "" class PluginFinder: _re_metadata_item = re.compile(r"\n\$(\w+) (.+)(?=\n\$|$)", re.MULTILINE) protocol_plugins = [ "http", "hls", "dash", ] def __init__(self): plugins_path = Path(streamlink_plugins.__path__[0]) self.plugins = [ (pname, plugins_path / f"{pname}.py") for finder, pname, ispkg in pkgutil.iter_modules([str(plugins_path)]) if not pname.startswith("common_") and pname not in self.protocol_plugins ] def get_plugins(self): for pluginname, pluginfile in self.plugins: pluginmetadata = self._parse_plugin(pluginname, pluginfile) if pluginmetadata: yield pluginmetadata def _parse_plugin(self, pluginname: str, pluginfile: Path) -> PluginMetadata | None: with pluginfile.open() as handle: # read until the first token has been parsed for tokeninfo in tokenize.generate_tokens(handle.readline): # the very first token needs to be a string / block comment with the metadata if tokeninfo.type != tokenize.STRING or not self._re_metadata_item.search(tokeninfo.string): return None metadata = tokeninfo.string.strip() break try: # continue reading the plugin file with the same handle pluginsource = handle.read() # build AST from plugin source for finding the used plugin arguments pluginast = ast.parse(pluginsource, str(pluginfile)) pluginmetadata = PluginMetadata(pluginname, pluginast) for item in self._re_metadata_item.finditer(metadata): key, value = item.groups() pluginmetadata.set(key, value) return pluginmetadata except Exception as err: raise ExtensionError(f"Error while parsing plugin file {pluginfile.name}", err) from err class PluginsDirective(Directive): def run(self): pluginfinder = PluginFinder() node = nodes.section() node.document = self.state.document result = ViewList() for pluginmetadata in pluginfinder.get_plugins(): for line in pluginmetadata.generate(): result.append(line, "plugins") nested_parse_with_titles(self.state, result, node) return node.children def setup(app): app.add_directive("plugins", PluginsDirective) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/ext_releaseref.py0000644000175100001660000000134415003227510017571 0ustar00runnerdocker"""Creates links that allows substituting the current release within the title or target.""" import os.path from docutils import nodes from sphinx.util.nodes import split_explicit_title def releaseref_role(name, rawtext, text, lineno, inliner, options=None, content=None): config = inliner.document.settings.env.config text = text.replace("|version|", config.version) text = text.replace("|release|", config.release) has_explicit_title, title, target = split_explicit_title(text) if not has_explicit_title: title = os.path.basename(target) node = nodes.reference(rawtext, title, refuri=target, **(options or {})) return [node], [] def setup(app): app.add_role("releaseref", releaseref_role) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/index.rst0000644000175100001660000000545115003227510016066 0ustar00runnerdockerStreamlink ========== Overview -------- Streamlink is a :ref:`command-line utility ` which pipes video streams from various services into a video player, such as `VLC`_ or `mpv`_. The main purpose of Streamlink is to avoid resource-heavy and unoptimized websites, while still allowing the user to enjoy various streamed content. There is also a :ref:`Python API ` available for developers who want access to the stream data. This project was forked from Livestreamer, which is no longer maintained. :octicon:`tag` Latest release (|version|) https://github.com/streamlink/streamlink/releases/latest :octicon:`mark-github` GitHub https://github.com/streamlink/streamlink :octicon:`issue-opened` Issue tracker https://github.com/streamlink/streamlink/issues :octicon:`comment-discussion` Discussion forum https://github.com/streamlink/streamlink/discussions :octicon:`comment-discussion` Gitter/Matrix channel `streamlink/streamlink:gitter.im `_ :octicon:`package` PyPI https://pypi.org/project/streamlink/ :octicon:`law` Free software Simplified BSD license .. _VLC: https://www.videolan.org/ .. _mpv: https://mpv.io/ Features -------- Streamlink is built on top of a plugin system which allows support for new services to be added easily. Most of the popular streaming services are supported, such as `Twitch `_, `YouTube `_, and many more. A list of all plugins currently included can be found on the :ref:`plugins ` page. Quickstart ---------- The default behavior of Streamlink is to play back streams in the `VLC`_ player. .. sourcecode:: console $ streamlink twitch.tv/day9tv best [cli][info] Found matching plugin twitch for URL twitch.tv/day9tv [cli][info] Available streams: audio_only, 160p (worst), 360p, 480p, 720p, 720p60, 1080p60 (best) [cli][info] Opening stream: 1080p60 (hls) [cli][info] Starting player: vlc For more in-depth usage and install instructions, please refer to the `User guide`_. User guide ---------- Streamlink is made up of two parts, a :ref:`cli ` and a library :ref:`API `. See their respective sections for more information on how to use them. Thank you --------- - `Github `_, for hosting the git repo, docs, release assets and providing CI tools - `Whatismybrowser `_, for the access to their user-agents API in our CI workflows Table of contents ----------------- .. toctree:: :maxdepth: 2 Overview install cli plugins players deprecations migrations developing api_guide api changelog support applications thirdparty ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/install.rst0000644000175100001660000006310015003227510016420 0ustar00runnerdocker.. |br| raw:: html
Installation ============ .. sphinx-design currently doesn't support autosectionlabel, so set labels for the following sections explicitly .. grid:: 4 :padding: 0 :class-container: installation-grid .. grid-item-card:: :link: windows :link-type: ref :link-alt: Windows :padding: 3 :text-align: center :fab:`windows` .. grid-item-card:: :link: macos :link-type: ref :link-alt: macOS :padding: 3 :text-align: center :fab:`apple` .. grid-item-card:: :link: linux-and-bsd :link-type: ref :link-alt: Linux and BSD :padding: 3 :text-align: center :fab:`linux` .. grid-item-card:: :link: pypi-package-and-source-code :link-type: ref :link-alt: PyPI package and source code :padding: 3 :text-align: center :fab:`python` .. _windows: Windows ------- .. list-table:: :header-rows: 1 :class: table-custom-layout * - Method - Installing * - :octicon:`verified` Installers - See the `Windows binaries`_ section below * - :octicon:`verified` Portable - See the `Windows binaries`_ section below * - :octicon:`verified` Preview builds - See the `Windows binaries`_ section below * - :octicon:`verified` Python pip - See the `PyPI package and source code`_ section below * - :octicon:`package` `Chocolatey`_ - .. code-block:: bat choco install streamlink `Installing Chocolatey packages`_ * - :octicon:`package-dependents` `Scoop`_ - .. code-block:: scoop bucket add extras scoop install streamlink `Installing Scoop packages`_ * - :octicon:`package-dependents` `Windows Package Manager`_ - .. code-block:: bat winget install streamlink `Installing Winget packages`_ .. _Chocolatey: https://chocolatey.org/packages/streamlink .. _Scoop: https://scoop.sh/#/apps?q=streamlink&s=0&d=1&o=true .. _Windows Package Manager: https://github.com/microsoft/winget-pkgs/tree/master/manifests/s/Streamlink/Streamlink .. _Installing Chocolatey packages: https://chocolatey.org .. _Installing Scoop packages: https://scoop.sh .. _Installing Winget packages: https://docs.microsoft.com/en-us/windows/package-manager/ .. _macos: macOS ----- .. list-table:: :header-rows: 1 :class: table-custom-layout * - Method - Installing * - :octicon:`verified` Python pip - See the `PyPI package and source code`_ section below * - :octicon:`package-dependents` `Homebrew`_ - .. code-block:: bash brew install streamlink `Installing Homebrew packages`_ .. _Homebrew: https://formulae.brew.sh/formula/streamlink .. _Installing Homebrew packages: https://brew.sh .. _linux-and-bsd: Linux and BSD ------------- .. list-table:: :header-rows: 1 :class: table-custom-layout * - Method / Distribution - Installing * - :octicon:`verified` AppImage - See the `Linux AppImages`_ section below * - :octicon:`verified` AppImage preview builds - See the `Linux AppImages`_ section below * - :octicon:`verified` Python pip - See the `PyPI package and source code`_ section below * - :octicon:`package-dependents` `Alpine Linux (edge)`_ - .. code-block:: bash sudo apk add streamlink * - :octicon:`package-dependents` `ALT Linux (Sisyphus)`_ - .. code-block:: bash sudo apt-get install streamlink * - :octicon:`package-dependents` `Arch Linux`_ - .. code-block:: bash sudo pacman -S streamlink * - :octicon:`package-dependents` `Arch Linux (aur, git)`_ - .. code-block:: bash git clone https://aur.archlinux.org/streamlink-git.git cd streamlink-git makepkg -si `Installing AUR packages`_ * - :octicon:`package-dependents` `Debian (sid, testing)`_ - .. code-block:: bash sudo apt update sudo apt install streamlink * - :octicon:`package-dependents` `Debian (stable)`_ - .. code-block:: bash # If you don't have Debian backports already (see link below): echo "deb http://deb.debian.org/debian bookworm-backports main" | sudo tee "/etc/apt/sources.list.d/streamlink.list" sudo apt update sudo apt -t bookworm-backports install streamlink `Installing Debian backported packages`_ * - :octicon:`package-dependents` `Fedora`_ - .. code-block:: bash sudo dnf install streamlink * - :octicon:`package-dependents` `FreeBSD (pkg)`_ - .. code-block:: bash pkg install multimedia/streamlink * - :octicon:`package-dependents` `FreeBSD (ports)`_ - .. code-block:: bash cd /usr/ports/multimedia/streamlink make config install clean * - :octicon:`package-dependents` `Gentoo Linux`_ - .. code-block:: bash sudo emerge net-misc/streamlink * - :octicon:`package-dependents` `NixOS`_ - .. code-block:: bash nix-env -iA nixos.streamlink `NixOS channel`_ * - :octicon:`package-dependents` `openSUSE`_ - .. code-block:: bash sudo zypper install streamlink * - :octicon:`package-dependents` `Ubuntu`_ - Not recommended. Ubuntu inherits the ``streamlink`` package from Debian, but doesn't maintain or update it. See `Package availability`_. * - :octicon:`package-dependents` `Solus`_ - .. code-block:: bash sudo eopkg install streamlink * - :octicon:`package-dependents` `Void`_ - .. code-block:: bash sudo xbps-install streamlink .. _Alpine Linux (edge): https://pkgs.alpinelinux.org/packages?name=streamlink .. _ALT Linux (Sisyphus): https://packages.altlinux.org/en/sisyphus/srpms/streamlink/ .. _Arch Linux: https://archlinux.org/packages/extra/any/streamlink/ .. _Arch Linux (aur, git): https://aur.archlinux.org/packages/streamlink-git/ .. _Debian (sid, testing): https://packages.debian.org/sid/streamlink .. _Debian (stable): https://packages.debian.org/bookworm-backports/streamlink .. _Fedora: https://src.fedoraproject.org/rpms/python-streamlink .. _FreeBSD (pkg): https://ports.freebsd.org/cgi/ports.cgi?query=streamlink&stype=name .. _FreeBSD (ports): https://www.freshports.org/multimedia/streamlink .. _Gentoo Linux: https://packages.gentoo.org/package/net-misc/streamlink .. _NixOS: https://github.com/NixOS/nixpkgs/tree/master/pkgs/by-name/st/streamlink .. _openSUSE: https://build.opensuse.org/package/show/multimedia:apps/streamlink .. _Ubuntu: https://packages.ubuntu.com/noble/streamlink .. _Solus: https://github.com/getsolus/packages/tree/main/packages/s/streamlink .. _Void: https://github.com/void-linux/void-packages/tree/master/srcpkgs/streamlink .. _Installing AUR packages: https://wiki.archlinux.org/index.php/Arch_User_Repository .. _Installing Debian backported packages: https://wiki.debian.org/Backports .. _NixOS channel: https://search.nixos.org/packages?show=streamlink&query=streamlink Package maintainers ------------------- .. list-table:: :header-rows: 1 :class: table-custom-layout * - Distribution / Platform - Maintainer * - Alpine Linux - Hoang Nguyen * - ALT Linux - Vitaly Lipatov * - Arch - Giancarlo Razzolini * - Arch (aur, git) - Josip Ponjavic * - Chocolatey - Scott Walters * - Debian - Alexis Murzeau * - Fedora - Mohamed El Morabity * - FreeBSD - Takefu * - Gentoo - soredake * - NixOS - Tuomas Tynkkynen * - openSUSE - Simon Puchert * - Solus - Joey Riches * - Void - Tom Strausbaugh * - Windows binaries - Sebastian Meyer * - Linux AppImages - Sebastian Meyer Package availability -------------------- Packaging is not done by the Streamlink maintainers themselves except for the `PyPI package `_, the `Windows installers + portable builds `_, and the `Linux AppImages `_. If a packaged release of Streamlink is not available for your operating system / distro or your system's architecture, or if it's out of date or broken, then please contact the respective package maintainers or package-repository maintainers of your operating system / distro, as it's up to them to add, update, or fix those packages. Users of glibc-based Linux distros can find up-to-date Streamlink releases via the available `AppImages `_. Please open an issue or pull request on GitHub if an **available**, **maintained** and **up-to-date** package is missing from the install docs. .. _pypi-package-and-source-code: PyPI package and source code ---------------------------- If a package is not available on your platform, or if it's out of date, then Streamlink can be installed via `pip`_, the Python package manager. .. warning:: On Linux, when not using a `virtual environment`_, it is recommended to **install custom python packages like this only for the current user** using the ``--user`` parameter, since system-wide packages can cause conflicts with the system's regular package manager. Never install Python packages via sudo in your system's global Python environment. User-packages will be installed into ``~/.local`` instead of ``/usr``, and entry-scripts for running the programs can be found in ``~/.local/bin``, e.g. ``~/.local/bin/streamlink``. In order for the command line shell to be able to find these executables, the user's ``PATH`` environment variable needs to be extended. This can be done by adding ``export PATH="${HOME}/.local/bin:${PATH}"`` to ``~/.profile`` or ``~/.bashrc``. .. list-table:: :header-rows: 1 :class: table-custom-layout * - Version - Installing * - :octicon:`verified` `Latest release`_ - .. code-block:: bash pip install -U streamlink * - :octicon:`verified` `Master branch`_ - .. code-block:: bash pip install -U git+https://github.com/streamlink/streamlink.git * - :octicon:`unverified` `From a pull request `_ - .. code-block:: bash pip install -U git+https://github.com/streamlink/streamlink.git@refs/pull/PULL-REQUEST-ID/head * - :octicon:`unverified` `Specific tag/branch/commit `_ - .. code-block:: bash pip install -U git+https://github.com/USERNAME/streamlink.git@REVISION .. _pip: https://pip.pypa.io/en/stable/ .. _Latest release: https://pypi.python.org/pypi/streamlink .. _Master branch: https://github.com/streamlink/streamlink/commits/master .. _pip-install-vcs: https://pip.pypa.io/en/stable/topics/vcs-support/#git Virtual environment ------------------- A better way of installing Streamlink as a non-system Python package is using the `venv`_ or `virtualenv`_ Python modules, which both create a user-owned Python environment which is isolated from the system's main Python environment. While `venv`_ is part of Python's standard library since ``3.3``, `virtualenv`_ is the project which `venv`_ originated from, but it first needs to be installed, either via `pip`_ or from the system's package manager. It also implements more features, so depending on your needs, you may want to use `virtualenv`_ instead of `venv`_. Install using ``venv`` and ``pip`` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: bash # Create a new environment python -m venv ~/myenv # Activate the environment # note: non-POSIX compliant shells like FISH or PowerShell have different activation script file names # note: on Windows, the `bin` subdirectory is called `Scripts` source ~/myenv/bin/activate # *Either* install the latest Streamlink release from PyPI in the virtual environment pip install --upgrade streamlink # *Or*, install the most up-to-date development version from master on GitHub pip install --upgrade git+https://github.com/streamlink/streamlink.git # Use Streamlink in the environment streamlink ... # Deactivate the environment deactivate # Use Streamlink without activating the environment ~/myenv/bin/streamlink ... Install using ``pipx`` ^^^^^^^^^^^^^^^^^^^^^^ The `pipx`_ project combines the functionality of both ``venv`` and ``pip``. It may be necessary to install it first, either with a system package manager, or using ``pip``, as detailed in the `documentation `_. .. code-block:: bash # *Either* install the latest Streamlink release from PyPI in a virtual environment pipx install streamlink # *Or*, install the most up-to-date development version from master on GitHub pipx install git+https://github.com/streamlink/streamlink.git # Use Streamlink streamlink ... .. _venv: https://docs.python.org/3/library/venv.html .. _virtualenv: https://virtualenv.pypa.io/en/stable/ .. _pipx: https://pypa.github.io/pipx/ Source distribution ------------------- In addition to the pre-built wheels uploaded to PyPI, Streamlink's source distribution tarballs get uploaded to both PyPI and GitHub releases. These tarballs are meant for packagers and are signed using the following PGP key: :bdg-link-primary-line:`44448A298D5C3618 ` Please be aware that PyPI has dropped support for uploading new release file signatures in May 2023, so those can only be found on `GitHub releases`_ now. See the `Dependencies`_ section down below for the required build- and runtime-requirements. .. warning:: Please avoid building Streamlink from tarballs generated by GitHub from (tagged) git commits, as they are lacking the built-in release version string. The ``versioningit`` build-requirement also won't be able to find the correct version, as the content is not part of a git repository. Instead, build from Streamlink's signed source-distribution tarballs which are uploaded to PyPI and GitHub releases, or from the cloned git repository. .. _GitHub Releases: https://github.com/streamlink/streamlink/releases Dependencies ^^^^^^^^^^^^ To install Streamlink from source, you will need these dependencies. Since :ref:`4.0.0 `, Streamlink defines a `build system `__ according to `PEP-517`_ / `PEP-518`_. .. list-table:: :header-rows: 1 :class: table-custom-layout table-custom-layout-dependencies * - Type - Name - Notes * - python - `Python`_ - At least version **3.9** * - build - `setuptools`_ - At least version **65.6.0** |br| Used as build backend * - build - `wheel`_ - Used by the build frontend for creating Python wheels * - build - `versioningit`_ - At least version **2.0.0** |br| Used for generating the version string from git when building, or when running in an editable install. Not needed when building wheels and installing from the source distribution. * - runtime - `certifi`_ - Used for loading the CA bundle extracted from the Mozilla Included CA Certificate List * - runtime - `exceptiongroup`_ - Only required when ``python_version<"3.11"`` |br| Used for ``ExceptionGroup`` handling * - runtime - `isodate`_ - Used for parsing ISO8601 strings * - runtime - `lxml`_ - Used for processing HTML and XML data * - runtime - `pycountry`_ - Used for localization settings, provides country and language data * - runtime - `pycryptodome`_ - Used for decrypting encrypted streams * - runtime - `PySocks`_ - Used for SOCKS Proxies * - runtime - `requests`_ - Used for making any kind of HTTP/HTTPS request * - runtime - `trio`_ - Used for async concurrency and I/O in some parts of Streamlink * - runtime - `trio-websocket`_ - Used for WebSocket connections on top of the async trio framework * - runtime - `urllib3`_ - Used internally by `requests`_, defined as direct dependency * - runtime - `websocket-client`_ - Used for making websocket connections * - optional - `FFmpeg`_ - Required for `muxing`_ multiple video/audio/subtitle streams into a single output stream. - DASH streams with video and audio content always have to get remuxed. - HLS streams optionally need to get remuxed depending on the stream selection. * - optional - | `brotli`_ | ``decompress`` extras marker - Used for decompressing HTTP responses * - optional - | `zstandard`_ | ``decompress`` extras marker - Used for decompressing HTTP responses .. _pyproject.toml: https://github.com/streamlink/streamlink/blob/master/pyproject.toml .. _PEP-517: https://peps.python.org/pep-0517/ .. _PEP-518: https://peps.python.org/pep-0518/ .. _Python: https://www.python.org/ .. _setuptools: https://setuptools.pypa.io/en/latest/ .. _wheel: https://wheel.readthedocs.io/en/stable/ .. _versioningit: https://versioningit.readthedocs.io/en/stable/ .. _certifi: https://certifiio.readthedocs.io/en/latest/ .. _exceptiongroup: https://github.com/agronholm/exceptiongroup .. _isodate: https://pypi.org/project/isodate/ .. _lxml: https://lxml.de/ .. _pycountry: https://pypi.org/project/pycountry/ .. _pycryptodome: https://pycryptodome.readthedocs.io/en/latest/ .. _PySocks: https://github.com/Anorov/PySocks .. _requests: https://requests.readthedocs.io/en/latest/ .. _trio: https://trio.readthedocs.io/en/stable/ .. _trio-websocket: https://trio-websocket.readthedocs.io/en/stable/ .. _urllib3: https://urllib3.readthedocs.io/en/stable/ .. _websocket-client: https://pypi.org/project/websocket-client/ .. _brotli: https://pypi.org/project/Brotli/ .. _zstandard: https://pypi.org/project/zstandard/ .. _FFmpeg: https://www.ffmpeg.org/ .. _muxing: https://en.wikipedia.org/wiki/Multiplexing#Video_processing Windows binaries ---------------- .. grid:: 2 :padding: 0 :class-container: grid-with-icons .. grid-item-card:: :padding: 3 :link: https://github.com/streamlink/windows-builds/releases :link-alt: Windows stable releases :text-align: center **Windows stable releases** ^^^ :fas:`download;fa-2x` GitHub releases page :fas:`check-circle;sd-text-success fa-lg` The most recent Streamlink release .. grid-item-card:: :padding: 3 :link: https://github.com/streamlink/windows-builds/actions/workflows/preview-build.yml :link-alt: Windows preview builds :text-align: center **Windows preview builds** ^^^ :fas:`download;fa-2x` GitHub actions build artifacts :fab:`github;fa-lg` Account required **Flavors** .. grid:: 2 :padding: 0 :class-container: grid-with-icons .. grid-item-card:: :padding: 3 :class-header: sd-text-center :class-body: sd-py-0 :class-footer: sd-text-center sd-bg-transparent sd-border-0 sd-pt-0 sd-pb-3 :fas:`gears` **Installer** ^^^ - Windows 10+ - Adds itself to the system's ``PATH`` env var - Automatically creates a :ref:`config file ` - Sets :option:`--ffmpeg-ffmpeg` in config file +++ :bdg-link-success-line:`x86_64 ` .. grid-item-card:: :padding: 3 :class-header: sd-text-center :class-body: sd-py-0 :class-footer: sd-text-center sd-bg-transparent sd-border-0 sd-pt-0 sd-pb-3 :fas:`file-zipper` **Portable archive** ^^^ - Windows 10+ - No :ref:`config file ` created automatically - :option:`--ffmpeg-ffmpeg` must be set manually - No pre-compiled Python bytecode +++ :bdg-link-success-line:`x86_64 ` **Contents** .. grid:: 3 :padding: 0 :class-container: grid-with-images .. grid-item-card:: :padding: 3 :link: https://github.com/streamlink/python-windows-embed :link-alt: Embedded Python build :text-align: center .. image:: _static/icon-python.svg :alt: Python Python |br| :sub:`embedded build` .. grid-item-card:: :padding: 3 :link: https://github.com/streamlink/streamlink :link-alt: Streamlink and its runtime dependencies :text-align: center .. image:: _static/icon.svg :alt: Streamlink Streamlink |br| :sub:`and dependencies` .. grid-item-card:: :padding: 3 :link: https://github.com/streamlink/FFmpeg-Builds :link-alt: FFmpeg, required for muxing streams :text-align: center .. image:: _static/icon-ffmpeg.svg :alt: FFmpeg FFmpeg |br| :sub:`for muxing streams` Linux AppImages --------------- .. grid:: 2 :padding: 0 :class-container: grid-with-icons .. grid-item-card:: :padding: 3 :link: https://github.com/streamlink/streamlink-appimage/releases :link-alt: AppImage stable releases :text-align: center **AppImage stable releases** ^^^ :fas:`download;fa-2x` GitHub releases page :fas:`check-circle;sd-text-success fa-lg` The most recent Streamlink release .. grid-item-card:: :padding: 3 :link: https://github.com/streamlink/streamlink-appimage/actions/workflows/preview-build.yml :link-alt: AppImage preview builds :text-align: center **AppImage preview builds** ^^^ :fas:`download;fa-2x` GitHub actions build artifacts :fab:`github;fa-lg` Account required **Architectures** .. grid:: 2 :padding: 0 .. grid-item-card:: :padding: 3 :text-align: center :bdg-link-success-line:`x86_64 ` .. grid-item-card:: :padding: 3 :text-align: center :bdg-link-success-line:`aarch64 ` **Contents** .. grid:: 3 :padding: 0 :class-container: grid-with-images .. grid-item-card:: :padding: 3 :link: https://github.com/streamlink/appimage-buildenv :link-alt: Python from the pypa/manulinux docker images :text-align: center .. image:: _static/icon-python.svg :alt: Python Python |br| :sub:`from the pypa/manylinux docker images` .. grid-item-card:: :padding: 3 :link: https://github.com/streamlink/streamlink :link-alt: Streamlink and its runtime dependencies :text-align: center .. image:: _static/icon.svg :alt: Streamlink Streamlink |br| :sub:`and dependencies` .. grid-item-card:: :padding: 3 :link: https://github.com/streamlink/FFmpeg-Builds :link-alt: FFmpeg, required for muxing streams :text-align: center .. image:: _static/icon-ffmpeg.svg :alt: FFmpeg FFmpeg |br| :sub:`for muxing streams` |br| :sub:`(optional)` **How-To** 1. Verify that the system is running on at least `glibc `_ `2.28 (Aug 2018) `_ (see :command:`ld.so --version`) 2. Download the AppImage file matching the system's CPU architecture (see :command:`uname --machine`) 3. Set the executable flag via a file browser or :command:`chmod +x filename` from a command-line shell .. code-block:: bash # AppImage file names include the release version, # the Python version, platform name and CPU architecture chmod +x streamlink-7.0.0-1-cp312-cp312-manylinux_2_28_x86_64.AppImage 4. Run the AppImage with any command-line parameters supported by Streamlink .. code-block:: bash ./streamlink-7.0.0-1-cp312-cp312-manylinux_2_28_x86_64.AppImage --loglevel=debug .. _glibc-wikipedia: https://en.wikipedia.org/wiki/Glibc .. _glibc-release-distro-mapping: https://sourceware.org/glibc/wiki/Release#Distribution_Branch_Mapping What are AppImages? ^^^^^^^^^^^^^^^^^^^ AppImages are portable applications which are independent of the Linux distribution in use and its package management. Just set the executable flag on the AppImage file and run it. The only requirement is having `FUSE`_ installed for being able to mount the contents of the AppImage's SquashFS, which is done automatically. Also, only glibc-based systems are currently supported. Note: Check out `AppImageLauncher`_, which automates the setup and system integration of AppImages. AppImageLauncher may also be available via your distro's package management. Additional information, like for example how to inspect the AppImage contents or how to extract the contents if `FUSE`_ is not available on your system, can be found in the `AppImage documentation`_. .. _AppImageLauncher: https://github.com/TheAssassin/AppImageLauncher .. _FUSE: https://docs.appimage.org/user-guide/troubleshooting/fuse.html .. _AppImage documentation: https://docs.appimage.org/user-guide/run-appimages.html .. _streamlink-master: https://github.com/streamlink/streamlink/commits/master ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/migrations.rst0000644000175100001660000003742315003227510017137 0ustar00runnerdockerMigrations ========== streamlink 7.0.0 ---------------- Config file paths ^^^^^^^^^^^^^^^^^ Support for the old and :ref:`deprecated ` config file paths, including plugin-specific config file paths, has been removed. | :octicon:`x-circle` #3766 | :octicon:`git-pull-request` #6149 .. admonition:: Migration :class: hint Please switch to the config file paths listed in the :ref:`Configuration file ` section of the CLI docs, or use the :option:`--config` CLI argument to set custom config file paths. Custom plugins sideloading paths ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Support for the old and :ref:`deprecated ` custom plugins sideloading paths has been removed. | :octicon:`x-circle` #3766 | :octicon:`git-pull-request` #6150 .. admonition:: Migration :class: hint Please see the plugins directory paths listed in the :ref:`Plugin sideloading ` section of the CLI docs, or use the :option:`--plugin-dir` CLI argument to set the path to another location. Stream-type related CLI arguments ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The :ref:`deprecated ` ``--hls-segment-{attempts,threads,timeout}`` and ``--{hls,http-stream}-timeout`` CLI arguments, as well as their :ref:`deprecated ` ``{dash,hls}-segment-{attempts,threads,timeout}`` and ``{dash,hls,http-stream}-timeout`` session option counterparts have been removed. | :octicon:`x-circle` #3893 | :octicon:`git-pull-request` #6232 .. admonition:: Migration :class: hint .. tab-set:: .. tab-item:: CLI arguments - use :option:`--stream-segment-attempts` instead of ``--hls-segment-attempts`` - use :option:`--stream-segment-threads` instead of ``--hls-segment-threads`` - use :option:`--stream-segment-timeout` instead of ``--hls-segment-timeout`` - use :option:`--stream-timeout` instead of ``--{hls,http-stream}-timeout`` .. tab-item:: Session options - use :class:`stream-segment-attempts ` instead of ``{dash,hls}-segment-attempts`` - use :class:`stream-segment-threads ` instead of ``{dash,hls}-segment-threads`` - use :class:`stream-segment-timeout ` instead of ``{dash,hls}-segment-timeout`` - use :class:`stream-timeout ` instead of ``{dash,hls,http-stream}-timeout`` HTTPSession and HTTPAdapters ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The :ref:`deprecated ` import paths for the ``HTTPSession`` class and custom ``HTTPAdapter`` classes have been removed. | :octicon:`x-circle` #5807 | :octicon:`git-pull-request` #6274 .. admonition:: Migration :class: hint Plugin implementors were never supposed to import the ``HTTPSession`` class directly, and instead should always reference the :attr:`http ` attribute of Streamlink's :attr:`session ` attribute in instances of ``Plugin``. Import Streamlink's custom ``HTTPAdapter`` classes from ``streamlink.session.http``. streamlink.plugins re-exports ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The :ref:`deprecated ` import paths for the :class:`NoPluginError `, :class:`NoStreamsError `, :class:`PluginError `, and :class:`Plugin ` classes have been removed. | :octicon:`x-circle` #6005 | :octicon:`git-pull-request` #6274 .. admonition:: Migration :class: hint Import error classes from ``streamlink.exceptions`` or ``streamlink.plugin``. Import the ``Plugin`` class from ``streamlink.plugin``. --force-progress ^^^^^^^^^^^^^^^^ The :ref:`deprecated ` ``--force-progress`` CLI argument has been removed. | :octicon:`x-circle` #5268 | :octicon:`git-pull-request` #6196 .. admonition:: Migration :class: hint Use :option:`--progress=force` instead of ``--force-progress``. streamlink 6.0.0 ---------------- Player-path-only --player CLI argument ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Despite having the dedicated CLI argument for setting custom player arguments :option:`--player-args`, Streamlink used to support setting custom player arguments using the :option:`--player` CLI argument. This meant that the :option:`--player` value had to be treated as a command line string rather than a player path. As a result of this, player paths would need to be quoted if they contained whitespace characters. While the default config file of Streamlink's Windows installer tried to teach this, it was often used incorrectly on command-line shells, especially on Windows where escaping the CLI argument is more difficult compared to POSIX compliant command-line shells. Not quoting the player path on Windows still worked, but at the cost of potentially resolving an incorrect or malicious executable. The support for custom player arguments in :option:`--player` was a relic from the Livestreamer project and has finally been removed. :option:`--player` now only accepts player executable path strings and any custom player arguments need to be set using the :option:`--player-args` CLI argument where the argument value gets properly interpreted using shell-like syntax. Streamlink's Windows installer has received a new default config file and users now also can choose to overwrite their existing config file from previous installs. The default behavior remains the same with existing config files staying untouched. | :octicon:`x-circle` #5305 | :octicon:`git-pull-request` #5310 .. admonition:: Migration :class: hint 1. Move any custom player arguments from the value of :option:`--player` to :option:`--player-args` 2. In config files, remove any quotation from the value of :option:`--player` (command-line shells will of course require quotation when the player path contains whitespace characters) {filename} variable in --player-args ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The :option:`--player-args`'s ``{filename}`` variable has been removed. This was kept as a fallback when the ``{playerinput}`` variable as added to prevent confusion around the player's input argument for various different stream transport methods, like stdin, named pipes, passthrough, etc. | :octicon:`x-circle` #3313 | :octicon:`git-pull-request` #5310 .. admonition:: Migration :class: hint 1. Rename ``{filename}`` to ``{playerinput}`` Plugin.can_handle_url() and Plugin.priority() ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Streamlink 2.3.0 :ref:`deprecated ` the ``can_handle_url()`` and ``priority()`` classmethods of :py:class:`Plugin ` in favor of the plugin matcher API. These deprecated classmethods have now been removed. | :octicon:`x-circle` #3814 | :octicon:`git-pull-request` #5403 .. admonition:: Migration :class: hint 1. Replace custom matching logic in ``Plugin.can_handle_url()`` with :py:meth:`pluginmatcher ` decorators 2. Replace custom plugin priority matching logic in ``Plugin.priority()`` with the ``priority`` argument of the :py:meth:`pluginmatcher ` decorators Plugin.__init__(self, url) compatibility wrapper ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Streamlink 5.0.0 :ref:`deprecated ` the usage of the old :py:class:`Plugin ` constructor without the :py:class:`Streamlink ` ``session`` argument. ``session`` was added because the old ``Plugin.bind()`` classmethod got removed, which previously bound the session instance to the entire ``Plugin`` class, rather than individual ``Plugin`` instances, causing Python's garbage collector to not be able to let go of any loaded built-in plugins when initializing more than one session. | :octicon:`x-circle` #4768 | :octicon:`git-pull-request` #5402 .. admonition:: Migration :class: hint 1. Replace the arguments of custom constructors of each :py:class:`Plugin ` subclass with ``*args, **kwargs`` and call ``super().__init__(*args, **kwargs)`` 2. If needed, access the ``url`` using ``self.url`` Streamlink.{g,s}et_plugin_option() ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The ``Streamlink.get_plugin_option()`` and ``Streamlink.set_plugin_option()`` methods were removed as a result of moving plugin options from the :py:class:`Plugin ` classes to individual ``Plugin`` instances. Plugin options now must be get/set referencing the :py:attr:`Plugin.options ` instance and its respective :py:meth:`get() ` and :py:meth:`set() ` methods. Alternatively, when initializing a :py:class:`Plugin ` class, e.g. after calling :py:meth:`Streamlink.resolve_url() ` or :py:meth:`Streamlink.streams() `, an optional pre-initialized instance of :py:class:`Options ` can be passed to the constructor of the resolved ``Plugin`` class. Be aware that ``Streamlink.resolve_url()`` will return the explicit plugin name, plugin class and the resolved URL, whereas ``Streamlink.streams()`` will initialize the first matching plugin automatically, so it's possible to pass custom options to a different plugin by accident, if the URL matches an unintended plugin. | :octicon:`git-pull-request` #5033 .. admonition:: Migration :class: hint 1. Initialize an :py:class:`Options ` object with the desired key-value pairs and pass it to the :py:class:`Plugin ` constructor or the :py:meth:`Streamlink.streams() ` method. 2. After instantiating a ``Plugin`` class, get or set its options using the ``get()``/``set()`` methods on the :py:attr:`Plugin.options ` instance. 3. If plugin options need to be accessed in custom :py:class:`Stream ` implementations related to custom ``Plugin`` implementations, then those options need to be passed from the ``Plugin`` to the ``Stream`` constructor beforehand, since the :py:class:`Streamlink ` session can't be used for that anymore. .. tab-set:: .. tab-item:: Before .. code-block:: python from streamlink.session import Streamlink session = Streamlink() session.set_plugin_option("twitch", "api-header", [("Authorization", "OAuth TOKEN")]) streams = session.streams("twitch.tv/...") .. tab-item:: After .. code-block:: python from streamlink.options import Options from streamlink.session import Streamlink session = Streamlink() options = Options() options.set("api-header", [("Authorization", "OAuth TOKEN")]) streams = session.streams("twitch.tv/...", options) .. tab-item:: Alternative .. code-block:: python from streamlink.options import Options from streamlink.session import Streamlink session = Streamlink() pluginname, Pluginclass, resolved_url = session.resolve_url("twitch.tv/...") options = Options() options.set("api-header", [("Authorization", "OAuth TOKEN")]) plugin = Pluginclass(session, resolved_url, options) streams = plugin.streams() Global plugin arguments ^^^^^^^^^^^^^^^^^^^^^^^ Streamlink 5.3.0 :ref:`deprecated ` the ``is_global=True`` argument of the :py:meth:`pluginargument ` decorator (as well as the :py:class:`Argument ` class), as global plugin arguments were deemed unnecessary. The ``is_global`` argument has thus been removed now. | :octicon:`x-circle` #5140 | :octicon:`git-pull-request` #5033 .. admonition:: Migration :class: hint 1. Get the value of the global argument using :py:meth:`Streamlink.get_option() ` instead of getting it from :py:attr:`Plugin.options ` plugin.api.validate.text ^^^^^^^^^^^^^^^^^^^^^^^^ Streamlink 5.2.0 :ref:`deprecated ` the ``plugin.api.validate.text`` alias for ``str``. This was a remnant of the Python 2 era and has been removed. | :octicon:`x-circle` #5090 | :octicon:`git-pull-request` #5404 .. admonition:: Migration :class: hint 1. Replace ``plugin.api.validate.text`` with ``str`` HTTPStream and HLSStream signature changes ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The signatures of the constructors of :py:class:`HTTPStream ` and :py:class:`HLSStream `, as well as the :py:meth:`HLSStream.parse_variant_playlist() ` classmethod were changed and fixed. | :octicon:`git-pull-request` #5429 .. admonition:: Migration :class: hint 1. Set the :py:class:`Streamlink ` session instance as a positional argument, or replace the ``session_`` keyword with ``session`` streamlink 5.0.0 ---------------- Session.resolve_url() return type changes ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ With the removal of the ``Plugin.bind()`` classmethod, the return value of :py:meth:`Streamlink.resolve_url() ` and :py:meth:`Streamlink.resolve_url_no_redirect() ` were changed. Both methods now return a three-element tuple of the resolved plugin name, plugin class and URL. | :octicon:`git-pull-request` #4768 .. admonition:: Migration :class: hint 1. Return type changed from ``tuple[type[Plugin], str]`` to ``tuple[str, type[Plugin], str]`` streamlink 4.0.0 ---------------- streamlink.plugin.api.utils ^^^^^^^^^^^^^^^^^^^^^^^^^^^ The ``streamlink.plugin.api.utils`` module has been removed, including the ``itertags`` function and the export aliases for ``streamlink.utils.parse``. | :octicon:`x-circle` #4455 | :octicon:`git-pull-request` #4467 .. admonition:: Migration :class: hint 1. Write validation schemas using the ``parse_{html,json,xml}()`` validators. Parsed HTML/XML documents enable data extraction with XPath queries. 2. Alternatively, import the ``parse_{html,json,qsd,xml}()`` utility functions from the ``streamlink.utils.parse`` module streamlink 3.0.0 ---------------- Plugin class returned by Session.resolve_url() ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ In order to enable :py:class:`Plugin ` constructors to have access to plugin options derived from the resolved plugin arguments, ``Plugin`` instantiation moved from :py:meth:`Streamlink.resolve_url() ` to ``streamlink_cli``, and the return value of :py:meth:`Streamlink.resolve_url() ` and :py:meth:`Streamlink.resolve_url_no_redirect() ` were changed. | :octicon:`git-pull-request` #4163 .. admonition:: Migration :class: hint 1. Return type changed from ``Plugin`` to ``tuple[type[Plugin], str]`` ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/players.rst0000644000175100001660000000651115003227510016434 0ustar00runnerdockerPlayers ======= Transport modes --------------- There are three different modes of transporting the stream to the player. .. list-table:: :header-rows: 1 :class: sd-w-100 * - Name - Description * - Standard input pipe - This is the default behavior when there are no other options specified. * - Named pipe (FIFO) - See the :option:`--player-fifo` option. * - HTTP - See the :option:`--player-http` and :option:`--player-continuous-http` options. .. note:: Streamlink also allows passing the resolved stream URL through to the player as its first launch argument when using the :option:`--player-passthrough` option. This does only work if the player has support the specific streaming protocol built in. The player will then do all the data fetching on its own while Streamlink will just wait for the player process to end. Some streaming protocols like DASH can't be passed through to the player. Player compatibility -------------------- This is a list of video players and their compatibility with the transport modes. .. list-table:: :header-rows: 1 :class: sd-w-100 * - Name - OS - License - stdin pipe - named pipe - HTTP * - `VLC media player`_ - :fab:`windows;fa-xl` :fab:`apple;fa-xl` :fab:`linux;fa-xl` - GPL2 / LGPL2.1 - :octicon:`thumbsup;1em;sd-text-success` [1]_ - :octicon:`thumbsup;1em;sd-text-success` - :octicon:`thumbsup;1em;sd-text-success` * - `mpv`_ - :fab:`windows;fa-xl` :fab:`apple;fa-xl` :fab:`linux;fa-xl` - GPL2 / LGPL2.1 - :octicon:`thumbsup;1em;sd-text-success` - :octicon:`thumbsup;1em;sd-text-success` - :octicon:`thumbsup;1em;sd-text-success` * - `MPlayer`_ - :fab:`windows;fa-xl` :fab:`apple;fa-xl` :fab:`linux;fa-xl` - GPL2 - :octicon:`thumbsup;1em;sd-text-success` - :octicon:`thumbsup;1em;sd-text-success` - :octicon:`thumbsup;1em;sd-text-success` * - `IINA`_ - :fab:`apple;fa-xl` - GPL3 - :octicon:`thumbsup;1em;sd-text-success` [2]_ - :octicon:`thumbsdown;1em;sd-text-danger` - :octicon:`thumbsdown;1em;sd-text-danger` .. [1] Some versions of VLC might be unable to use the stdin pipe and prints the error message VLC is unable to open the MRL 'fd://0' Use one of the other transport methods instead to work around this. .. [2] Requires the ``--stdin`` player argument (:option:`--player-args`) .. _VLC media player: https://videolan.org/ .. _mpv: https://mpv.io/ .. _MPlayer: https://mplayerhq.hu/ .. _IINA: https://iina.io/ Known issues and workarounds ---------------------------- MPlayer tries to play Twitch streams at the wrong FPS ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This is a bug in MPlayer, using the MPlayer fork `mpv`_ instead is recommended. Youtube Live does not work with VLC ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ VLC versions below 3 cannot play Youtube Live streams. Please update your player. You can also try using a different player. Youtube Live does not work with Mplayer ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Some versions of Mplayer cannot play Youtube Live streams. And errors like: .. code-block:: console Cannot seek backward in linear streams! Seek failed Switching to a recent fork such as mpv resolves the issue. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/plugins.rst0000644000175100001660000000140715003227510016435 0ustar00runnerdockerPlugins ======= This is a list of the currently built-in plugins and what URLs and features they support. Streamlink's primary focus is live streams, so VOD support is limited. The purpose of having plugins is to allow users of Streamlink to input URLs from specific websites or streaming services without knowing the actual stream URLs or implementations, while also automatically setting up certain HTTP session parameters or providing additional features via :ref:`CLI arguments `, like authentication, skipping ads, enabling low latency streaming, etc. In addition to the plugins listed below, any of the supported :ref:`streaming protocols ` can be played directly, like for example HLS and DASH. .. plugins:: ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/support.rst0000644000175100001660000000125415003227510016470 0ustar00runnerdockerSupport ------- If you think that Streamlink is useful and if you want to keep the project alive, then please consider supporting its maintainers and contributors by sending a small and optionally recurring tip via the available options listed on their GitHub profiles. Your support is very much appreciated, thank you! Useful links ============ - Streamlink's GitHub releases with the changelog summary and commit history of each release: https://github.com/streamlink/streamlink/releases - Streamlink's monthly repository activity: https://github.com/streamlink/streamlink/pulse/monthly - All Streamlink repositories: https://github.com/orgs/streamlink/repositories ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs/thirdparty.rst0000644000175100001660000000651415003227510017152 0ustar00runnerdocker.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! If you're a developer and want to add your project/application to this list, please 1. adhere to the same structure and format of the entries of the applications.rst file and this one 2. add your new entry to the bottom of the list 3. at least provide the required fields (in the same order) - "Description" (a brief text describing your application) - "Type" (e.g. Graphical User Interface, CLI wrapper, etc.) - "OS" (please use the available icons) - "Author" (if possible, include a link to the creator's GitHub/GitLab profile, etc. or a contact email address) - "Website" 4. use an image - in the jpeg or png format - with a static and reliable !!!https!!! URL (use GitHub or an image hoster like Imgur, etc.) - with a reasonable size and aspect ratio - with a decent compression quality - that is not too large (at most 1 MiB allowed, the smaller the better) - that is neutral and only shows your application 5. optionally add more fields like a URL to the source code repository, a larger info or features text, etc. Please be aware that the Streamlink team may edit and remove your entry at any time. Thank you! :) !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Third Party Applications ======================== Help us extend this list by sending us a pull request on GitHub. Thanks! .. content list start LiveProxy --------- :Description: Local proxy server between Streamlink and an URL :OS: :fab:`windows;fa-xl` :fab:`apple;fa-xl` :fab:`linux;fa-xl` :Author: `back-to `_ :Website: https://liveproxy.github.io :Source code: https://github.com/back-to/liveproxy :Info: LiveProxy allows Streamlink to be easy accessible from **m3u** playlists, it is also available for **Kodi Leia** and **Enigma2** devices. A detailed guide can be found on the website. Wtwitch ------- .. image:: https://user-images.githubusercontent.com/25400030/97115072-6951e600-16ec-11eb-88f7-51939c0d0bc1.jpg :align: center :alt: Wtwitch screenshot of checking subscriptions :Description: Browse Twitch streams; subscribe to streamers locally without an account :Type: TUI (terminal user interface) :OS: :fab:`apple;fa-xl` :fab:`linux;fa-xl` :Author: `Hunter Peavey `_ :Website: https://git.sr.ht/~krathalan/wtwitch :Info: wtwitch is a Bash program that lets you browse the top games on Twitch, the top streamers for a given game, and lets you check the status of streamers you subscribe to. Open streams easily with settings that save your preferred quality and player. A man page comes with the AUR package or can be compiled manually. OBS-Streamlink -------------- :Description: OBS source plugin for embedding streams using Streamlink :Type: OBS Plugin :OS: :fab:`windows;fa-xl` :Author: `DD Center `_ :Website: https://github.com/dd-center/obs-streamlink :Info: OBS-Streamlink is a plugin for OBS (Open Broadcaster Software) which allows embedding streams directly as a scene source. This is useful for broadcasters of multi-platform live streams and for live commentators. .. content list end ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/docs-requirements.txt0000644000175100001660000000051415003227510017502 0ustar00runnerdocker# setup setuptools >=64.0.0 # required for reading the version string when building from git versioningit >=2.0.0,<4 # required for reading the version string when building from git # build sphinx >=6.0.0,<9 # themes and extensions furo ==2024.08.06 myst-parser >=1.0.0,<5 sphinx-design >=0.5.0,<=0.6.1 # typing docutils-stubs ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/icon.svg0000644000175100001660000004424115003227510014746 0ustar00runnerdocker image/svg+xml ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745694560.0448198 streamlink-7.3.0/pyproject.toml0000644000175100001660000001665015003227540016217 0ustar00runnerdocker[build-system] requires = [ "setuptools >=77", "wheel", # The versioningit build-requirement gets removed from the source distribution, # as the version string is already built into it (see the onbuild versioningit hook): # "versioningit >=2.0.0,<4", ] # setuptools build-backend override # https://setuptools.pypa.io/en/stable/build_meta.html backend-path = ["build_backend"] build-backend = "__init__" # https://peps.python.org/pep-0621/ # https://packaging.python.org/en/latest/specifications/declaring-project-metadata/ [project] name = "streamlink" description = """\ Streamlink is a command-line utility that extracts streams from various services and pipes them into a video player of choice.\ """ readme = "README.md" license = "BSD-2-Clause" license-files = ["LICENSE"] authors = [ { name = "Streamlink" }, { email = "streamlink@protonmail.com" } ] classifiers = [ "Development Status :: 5 - Production/Stable", "Environment :: Console", "Intended Audience :: End Users/Desktop", "Operating System :: POSIX", "Operating System :: MacOS", "Operating System :: Microsoft :: Windows", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Topic :: Internet :: WWW/HTTP", "Topic :: Multimedia :: Sound/Audio", "Topic :: Multimedia :: Video", "Topic :: Utilities", ] dynamic = [ "version", "entry-points", "scripts", "gui-scripts", ] requires-python = ">=3.9" dependencies = [ "certifi", "exceptiongroup ; python_version<'3.11'", "isodate", "lxml >=4.6.4,<6", "pycountry", "pycryptodome >=3.4.3,<4", "PySocks >=1.5.6,!=1.5.7", "requests >=2.26.0,<3", "trio >=0.25.0,<1 ; python_version>='3.13'", "trio >=0.22.0,<1 ; python_version<'3.13'", "trio-websocket >=0.9.0,<1", "urllib3 >=1.26.0,<3", "websocket-client >=1.2.1,<2", ] [project.optional-dependencies] decompress = [ # don't define brotli or zstandard library versions here # and instead rely on urllib3's version requirements "urllib3[brotli,zstd] >=1.26.0,<3" ] [project.urls] Homepage = "https://github.com/streamlink/streamlink" Documentation = "https://streamlink.github.io/" Tracker = "https://github.com/streamlink/streamlink/issues" Source = "https://github.com/streamlink/streamlink" Funding = "https://streamlink.github.io/latest/support.html" # https://setuptools.pypa.io/en/stable/userguide/pyproject_config.html # https://setuptools.pypa.io/en/stable/userguide/package_discovery.html # https://setuptools.pypa.io/en/stable/userguide/datafiles.html [tool.setuptools.packages.find] where = ["src"] namespaces = false [tool.setuptools.package-data] streamlink = [ "py.typed", ] "streamlink.plugins" = [ "_plugins.json", ] # https://versioningit.readthedocs.io/en/stable/index.html [tool.versioningit] # Packagers: don't patch the `default-version` string while using the tarball built by GitHub from the tagged git commit! # Instead, use Streamlink's signed source distribution as package source, which has the correct version string built in. # This fallback `default-version` string is only used when not building from the sdist or a git repo with at least one tag. # See the versioningit comment at the very top of this file! default-version = "0.0.0+unknown" [tool.versioningit.vcs] method = "git" [tool.versioningit.format] distance = "{base_version}+{distance}.{vcs}{rev}" dirty = "{base_version}+{distance}.{vcs}{rev}.dirty" distance-dirty = "{base_version}+{distance}.{vcs}{rev}.dirty" [tool.versioningit.next-version] method = "null" [tool.versioningit.onbuild] # When building the sdist or wheel, remove versioningit build-requirement and set the static version string method = { module = "build_backend.onbuild", value = "onbuild" } # https://docs.pytest.org/en/latest/reference/customize.html#configuration # https://docs.pytest.org/en/latest/reference/reference.html#ini-options-ref [tool.pytest.ini_options] testpaths = [ "build_backend", "tests", ] filterwarnings = [ "always", ] # https://coverage.readthedocs.io/en/latest/config.html [tool.coverage.run] branch = true source = [ "build_backend", "src", "tests", ] [tool.coverage.report] omit = [ "build_backend/commands.py", "src/streamlink/packages/*", "src/streamlink/webbrowser/cdp/devtools/*", "src/streamlink_cli/packages/*", "src/streamlink_cli/_parser.py", ] exclude_lines = [ "pragma: no cover", "def __repr__", "raise NotImplementedError", "if __name__ == \"__main__\":", "if (?:typing\\.)?TYPE_CHECKING:", ] # https://docs.astral.sh/ruff/configuration/ [tool.ruff] src = [ "build_backend", "src", "tests", ] fix = false line-length = 128 output-format = "full" preview = true extend-exclude = [ "docs/conf.py", "src/streamlink/packages/", "src/streamlink_cli/packages/", ] [tool.ruff.format] preview = true line-ending = "lf" quote-style = "double" skip-magic-trailing-comma = false exclude = [ "src/streamlink/session/http_useragents.py", "src/streamlink/webbrowser/cdp/devtools/*.py", ] [tool.ruff.lint] select = [ # pycodestyle "E", "W", # pyflakes "F", # pylint "PLC", "PLE", "PLW", # isort "I", # flake8-async "ASYNC", # flake8-builtins "A", # flake8-bugbear "B", # flake8-commas "COM", # flake8-comprehensions "C4", # flake8-datetimez "DTZ", # flake8-implicit-str-concat "ISC", # flake8-no-pep420 "INP", # flake8-logging "LOG", # flake8-pie "PIE", # flake8-print "T20", # flake8-pytest-style "PT", # flake8-quotes "Q", # flake8-tidy-imports "TID", # Ruff-specific rules "RUF", ] ignore = [ "A003", # builtin-attribute-shadowing "A005", # stdlib-module-shadowing "ASYNC109", # async-function-with-timeout "C408", # unnecessary-collection-call "ISC003", # explicit-string-concatenation "PLC1901", # compare-to-empty-string "PLC2801", # unnecessary-dunder-call "PLW2901", # redefined-loop-name "RUF021", # parenthesize-chained-operators "RUF022", # unsorted-dunder-all "RUF027", # missing-f-string-syntax ] [tool.ruff.lint.per-file-ignores] "__init__.py" = ["F401"] "docs/**" = ["INP", "RUF012"] "script/**" = ["INP"] "src/streamlink/__init__.py" = ["I"] "src/streamlink/_version.py" = ["I"] "src/streamlink/plugins/*" = ["RUF012"] "src/streamlink/session/http_useragents.py" = ["E501"] "src/streamlink/webbrowser/cdp/devtools/*" = ["E303", "E501", "F401"] "src/streamlink_cli/main.py" = ["PLW0603"] "tests/**" = ["RUF012"] [tool.ruff.lint.isort] known-first-party = ["streamlink", "streamlink_cli"] lines-after-imports = 2 combine-as-imports = true [tool.ruff.lint.flake8-builtins] builtins-ignorelist = [ "BaseExceptionGroup", # compat import from exceptiongroup pkg "ExceptionGroup", # compat import from exceptiongroup pkg ] [tool.ruff.lint.flake8-tidy-imports] ban-relative-imports = "all" [tool.ruff.lint.flake8-pytest-style] fixture-parentheses = true mark-parentheses = true [tool.ruff.lint.flake8-quotes] avoid-escape = false [tool.ruff.lint.flake8-implicit-str-concat] allow-multiline = false # https://mypy.readthedocs.io/en/stable/config_file.html [tool.mypy] python_version = 3.9 show_error_codes = true show_error_context = true show_column_numbers = true warn_no_return = false files = [ "build_backend", "script/", "src/streamlink/", "src/streamlink_cli/", "tests/", "setup.py", ] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745694559.9468186 streamlink-7.3.0/script/0000755000175100001660000000000015003227540014577 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/script/build-shell-completions.sh0000755000175100001660000000144215003227510021672 0ustar00runnerdocker#!/usr/bin/env bash set -e ROOT=$(git rev-parse --show-toplevel 2>/dev/null || realpath "$(dirname "$(readlink -f "${0}")")/..") DIST="${ROOT}/completions" PYTHON_DEPS=(streamlink_cli shtab) declare -A COMPLETIONS=( [bash]="streamlink" # ZSH requires the file to be prefixed with an underscore [zsh]="_streamlink" ) for dep in "${PYTHON_DEPS[@]}"; do python -c "import ${dep}" 2>&1 >/dev/null \ || { echo >&2 "${dep} could not be found in your python environment"; exit 1; } done for shell in "${!COMPLETIONS[@]}"; do mkdir -p "${DIST}/${shell}" dist="${DIST}/${shell}/${COMPLETIONS[${shell}]}" python -m shtab \ "--shell=${shell}" \ --error-unimportable \ streamlink_cli._parser.get_parser \ > "${dist}" echo "Completions for ${shell} written to ${dist}" done ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745694560.0418198 streamlink-7.3.0/setup.cfg0000644000175100001660000000004615003227540015114 0ustar00runnerdocker[egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745694560.0448198 streamlink-7.3.0/setup.py0000644000175100001660000000636315003227540015015 0ustar00runnerdocker#!/usr/bin/env python import sys from os import path from textwrap import dedent def format_msg(text, *args, **kwargs): return dedent(text).strip(" \n").format(*args, **kwargs) CURRENT_PYTHON = sys.version_info[:2] REQUIRED_PYTHON = (3, 9) # This check and everything above must remain compatible with older Python versions if CURRENT_PYTHON < REQUIRED_PYTHON: sys.exit( format_msg( """ ======================================================== Unsupported Python version ======================================================== This version of Streamlink requires at least Python {}.{}, but you're trying to install it on Python {}.{}. This may be because you are using a version of pip that doesn't understand the python_requires classifier. Make sure you have pip >= 9.0 and setuptools >= 24.2 """, *(REQUIRED_PYTHON + CURRENT_PYTHON), ), ) # Explicitly disable running tests via setuptools if "test" in sys.argv: sys.exit( format_msg(""" Running `python setup.py test` has been deprecated since setuptools 41.5.0. Streamlink requires pytest for collecting and running tests, via one of these commands: `pytest` or `python -m pytest` (see the pytest docs for more infos about this) """), ) def is_wheel_for_windows(argv): if "bdist_wheel" in argv: names = ["win32", "win-amd64", "cygwin"] length = len(argv) for pos in range(argv.index("bdist_wheel") + 1, length): if argv[pos] == "--plat-name" and pos + 1 < length: return argv[pos + 1] in names elif argv[pos][:12] == "--plat-name=": return argv[pos][12:] in names return False entry_points = { "console_scripts": ["streamlink=streamlink_cli.main:main"], } if is_wheel_for_windows(sys.argv): entry_points["gui_scripts"] = ["streamlinkw=streamlink_cli.main:main"] # optional data files data_files = [ # shell completions # requires pre-built completion files via shtab (dev-requirements.txt) # `./script/build-shell-completions.sh` ("share/bash-completion/completions", ["completions/bash/streamlink"]), ("share/zsh/site-functions", ["completions/zsh/_streamlink"]), # man page # requires pre-built man page file via sphinx (docs-requirements.txt) # `make --directory=docs clean man` ("share/man/man1", ["docs/_build/man/streamlink.1"]), ] data_files = [ (destdir, [file for file in srcfiles if path.exists(file)]) for destdir, srcfiles in data_files ] # fmt: skip if __name__ == "__main__": sys.path.insert(0, path.dirname(__file__)) from build_backend.commands import cmdclass from setuptools import setup try: # versioningit is only required when building from git (see pyproject.toml) from versioningit import get_cmdclasses except ImportError: # pragma: no cover def get_cmdclasses(_): # type: ignore[misc] return _ setup( cmdclass=get_cmdclasses(cmdclass), entry_points=entry_points, data_files=data_files, version="7.3.0", ) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745694559.9288182 streamlink-7.3.0/src/0000755000175100001660000000000015003227540014062 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745694559.9488187 streamlink-7.3.0/src/streamlink/0000755000175100001660000000000015003227540016233 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/src/streamlink/__init__.py0000644000175100001660000000133615003227510020344 0ustar00runnerdocker"""Streamlink extracts streams from various services. The main compontent of Streamlink is a command-line utility that launches the streams in a video player. An API is also provided that allows direct access to stream data. Full documentation is available at https://streamlink.github.io. """ from streamlink._version import __version__ __title__ = "streamlink" __license__ = "Simplified BSD" __author__ = "Streamlink" __copyright__ = "Copyright 2025 Streamlink" __credits__ = ["https://github.com/streamlink/streamlink/blob/master/AUTHORS"] from streamlink.api import streams from streamlink.exceptions import StreamlinkError, PluginError, NoStreamsError, NoPluginError, StreamError from streamlink.session import Streamlink ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/src/streamlink/__main__.py0000644000175100001660000000012015003227510020313 0ustar00runnerdockerif __name__ == "__main__": from streamlink_cli.main import main main() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745694560.0448198 streamlink-7.3.0/src/streamlink/_version.py0000644000175100001660000000002615003227540020427 0ustar00runnerdocker__version__ = "7.3.0" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/src/streamlink/api.py0000644000175100001660000000116615003227510017357 0ustar00runnerdockerfrom streamlink.session import Streamlink def streams(url: str, **params): """ Initializes an empty Streamlink session, attempts to find a plugin and extracts streams from the URL if a plugin was found. :param url: a URL to match against loaded plugins :param params: Additional keyword arguments passed to :meth:`Streamlink.streams() ` :raises NoPluginError: on plugin resolve failure :returns: A :class:`dict` of stream names and :class:`Stream ` instances """ session = Streamlink() return session.streams(url, **params) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/src/streamlink/buffers.py0000644000175100001660000000754015003227510020244 0ustar00runnerdockerfrom collections import deque from io import BytesIO from threading import Event, Lock class Chunk(BytesIO): """A single chunk, part of the buffer.""" def __init__(self, buf): self.length = len(buf) BytesIO.__init__(self, buf) @property def empty(self): return self.tell() == self.length class Buffer: """Simple buffer for use in single-threaded consumer/filler. Stores chunks in a deque to avoid inefficient reallocating of large buffers. """ def __init__(self): self.chunks = deque() self.current_chunk = None self.closed = False self.length = 0 self.written_once = False def _iterate_chunks(self, size): bytes_left = size while bytes_left: try: current_chunk = self.current_chunk or Chunk(self.chunks.popleft()) except IndexError: break data = current_chunk.read(bytes_left) bytes_left -= len(data) if current_chunk.empty: self.current_chunk = None else: self.current_chunk = current_chunk yield data def write(self, data): if not self.closed: data = bytes(data) # Copy so that original buffer may be reused self.chunks.append(data) self.length += len(data) self.written_once = True def read(self, size=-1): if size < 0 or size > self.length: size = self.length if not size: return b"" data = b"".join(self._iterate_chunks(size)) self.length -= len(data) return data def close(self): self.closed = True class RingBuffer(Buffer): """Circular buffer for use in multi-threaded consumer/filler.""" def __init__(self, size=8192 * 4): Buffer.__init__(self) self.buffer_size = size self.buffer_lock = Lock() self.event_free = Event() self.event_free.set() self.event_used = Event() def _check_events(self): if self.length > 0: self.event_used.set() else: self.event_used.clear() if self.is_full: self.event_free.clear() else: self.event_free.set() def _read(self, size=-1): with self.buffer_lock: data = Buffer.read(self, size) self._check_events() return data def read(self, size=-1, block=True, timeout=None): if block and not self.closed: if not self.event_used.wait(timeout) and self.length == 0: raise OSError("Read timeout") return self._read(size) def write(self, data): if self.closed: return data_left = len(data) data_total = len(data) while data_left > 0: self.event_free.wait() if self.closed: return with self.buffer_lock: write_len = min(self.free, data_left) written = data_total - data_left Buffer.write(self, data[written : written + write_len]) data_left -= write_len self._check_events() def resize(self, size): with self.buffer_lock: self.buffer_size = size self._check_events() def wait_free(self, timeout=None): return self.event_free.wait(timeout) def wait_used(self, timeout=None): return self.event_used.wait(timeout) def close(self): Buffer.close(self) # Make sure we don't let a .write() and .read() block forever self.event_free.set() self.event_used.set() @property def free(self): return max(self.buffer_size - self.length, 0) @property def is_full(self): return self.free == 0 __all__ = ["Buffer", "RingBuffer"] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/src/streamlink/cache.py0000644000175100001660000001147015003227510017650 0ustar00runnerdockerfrom __future__ import annotations import json import os import shutil import tempfile from contextlib import suppress from datetime import datetime from pathlib import Path from time import time from typing import Any from streamlink.compat import is_win32 if is_win32: xdg_cache = os.environ.get("APPDATA", os.path.expanduser("~")) else: xdg_cache = os.environ.get("XDG_CACHE_HOME", os.path.expanduser("~/.cache")) # TODO: fix macOS path and deprecate old one (with fallback logic) CACHE_DIR = Path(xdg_cache) / "streamlink" # TODO: rewrite data structure # - replace prefix logic with namespaces # - change timestamps from (timezoned) epoch values to ISO8601 strings (UTC) # - add JSON schema information # - add translation logic, to keep backwards compatibility class Cache: def __init__( self, filename: str | Path, key_prefix: str = "", ): """ Caches Python values as JSON and prunes expired entries. :param filename: A file name or :class:`Path` object, relative to the cache directory :param key_prefix: Optional prefix for each key to be retrieved from or stored in the cache """ self.key_prefix = key_prefix self.filename = CACHE_DIR / Path(filename) self._cache: dict[str, dict[str, Any]] = {} def _load(self): self._cache = {} if self.filename.exists(): with suppress(Exception): with self.filename.open("r") as fd: data = json.load(fd) self._cache.update(**data) def _prune(self): now = time() pruned = [] for key, value in self._cache.items(): expires = value.get("expires", now) if expires <= now: pruned.append(key) for key in pruned: self._cache.pop(key, None) return len(pruned) > 0 def _save(self): fd, tempname = tempfile.mkstemp() fd = os.fdopen(fd, "w") try: json.dump(self._cache, fd, indent=2, separators=(",", ": ")) except Exception: raise finally: fd.close() # Silently ignore errors try: self.filename.parent.mkdir(exist_ok=True, parents=True) shutil.move(tempname, str(self.filename)) except OSError: with suppress(Exception): os.remove(tempname) def set( self, key: str, value: Any, expires: float = 60 * 60 * 24 * 7, expires_at: datetime | None = None, ) -> None: """ Store the given value using the key name and expiration time. Prunes the cache of all expired key-value pairs before setting the new key-value pair. :param key: A specific key name :param value: Any kind of value that can be JSON-serialized :param expires: Expiration time in seconds, with the default being one week :param expires_at: Optional expiration date, which overrides the expiration time """ self._load() self._prune() if self.key_prefix: key = f"{self.key_prefix}:{key}" if expires_at is None: expires += time() else: try: expires = expires_at.timestamp() except OverflowError: expires = 0 self._cache[key] = dict(value=value, expires=expires) self._save() def get( self, key: str, default: Any | None = None, ) -> Any: """ Attempt to retrieve the given key from the cache. Prunes the cache of all expired key-value pairs before retrieving the key's value. :param key: A specific key name :param default: An optional default value if no key was stored, or if it has expired :return: The retrieved value or optional default value """ self._load() if self._prune(): self._save() if self.key_prefix: key = f"{self.key_prefix}:{key}" if key in self._cache and "value" in self._cache[key]: return self._cache[key]["value"] else: return default def get_all(self) -> dict[str, Any]: """ Retrieve all cached key-value pairs. Prunes the cache of all expired key-value pairs first. :return: A dictionary of all cached key-value pairs. """ ret = {} self._load() if self._prune(): self._save() for key, value in self._cache.items(): if self.key_prefix: prefix = f"{self.key_prefix}:" else: prefix = "" if key.startswith(prefix): okey = key[len(prefix) :] ret[okey] = value["value"] return ret __all__ = ["Cache"] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/src/streamlink/compat.py0000644000175100001660000000522315003227510020067 0ustar00runnerdockerfrom __future__ import annotations import importlib import inspect import os import sys import warnings from collections.abc import Callable, Mapping from typing import Any try: from builtins import BaseExceptionGroup, ExceptionGroup # type: ignore[attr-defined] except ImportError: # pragma: no cover from exceptiongroup import BaseExceptionGroup, ExceptionGroup # type: ignore[import] from streamlink.exceptions import StreamlinkDeprecationWarning # compatibility import of charset_normalizer/chardet via requests<3.0 try: from requests.compat import chardet as charset_normalizer # type: ignore except ImportError: # pragma: no cover import charset_normalizer is_darwin = sys.platform == "darwin" is_win32 = os.name == "nt" detect_encoding = charset_normalizer.detect def deprecated(items: Mapping[str, tuple[str | None, Any, Any]]) -> None: """ Deprecate specific module attributes. This removes the deprecated attributes from the module's global context, adds/overrides the module's :func:`__getattr__` function, and emits a :class:`StreamlinkDeprecationWarning` if one of the deprecated attributes is accessed. :param items: A mapping of module attribute names to tuples which contain the following optional items: 1. an import path string (for looking up an external object while accessing the attribute) 2. a direct return object (if no import path was set) 3. a custom warning message """ mod_globals = inspect.stack()[1].frame.f_globals orig_getattr: Callable[[str], Any] | None = mod_globals.get("__getattr__", None) def __getattr__(name: str) -> Any: if name in items: origin = f"{mod_globals['__spec__'].name}.{name}" path, obj, msg = items[name] warnings.warn( msg or f"'{origin}' has been deprecated", StreamlinkDeprecationWarning, stacklevel=2, ) if path: *parts, name = path.split(".") imported = importlib.import_module(".".join(parts)) obj = getattr(imported, name, None) return obj if orig_getattr is not None: return orig_getattr(name) raise AttributeError mod_globals["__getattr__"] = __getattr__ # delete the deprecated module attributes and the imported `deprecated` function for item in items.keys() | [deprecated.__name__]: if item in mod_globals: del mod_globals[item] __all__ = [ "BaseExceptionGroup", "ExceptionGroup", "deprecated", "detect_encoding", "is_darwin", "is_win32", ] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/src/streamlink/exceptions.py0000644000175100001660000000352115003227510020764 0ustar00runnerdockerclass StreamlinkError(Exception): """ Any error caused by Streamlink will be caught with this exception. """ # TODO: don't use PluginError for failed HTTP requests or validation schema failures class PluginError(StreamlinkError): """ Plugin related error. """ class FatalPluginError(PluginError): """ Plugin related error that cannot be recovered from. Plugins should use this ``Exception`` when errors that can never be recovered from are encountered. For example, when a user's input is required and none can be given. """ class NoPluginError(StreamlinkError): """ Error raised by :py:meth:`Streamlink.resolve_url() ` and :py:meth:`Streamlink.resolve_url_no_redirect() ` when no plugin could be found for the given input URL. """ class NoStreamsError(StreamlinkError): """ Plugins should use this ``Exception`` in :py:meth:`Plugin._get_streams() ` when returning ``None`` or an empty ``dict`` is not possible, e.g. in nested function calls. """ class StreamError(StreamlinkError): """ Stream related error. """ # https://stackoverflow.com/a/49797717 class _StreamlinkWarningMeta(type): def __new__(mcs, name, bases, namespace, **kw): name = namespace.get("__name__", name) return super().__new__(mcs, name, bases, namespace, **kw) class StreamlinkWarning(UserWarning, metaclass=_StreamlinkWarningMeta): pass class StreamlinkDeprecationWarning(StreamlinkWarning): __name__ = "StreamlinkDeprecation" __all__ = [ "StreamlinkError", "PluginError", "FatalPluginError", "NoPluginError", "NoStreamsError", "StreamError", "StreamlinkWarning", "StreamlinkDeprecationWarning", ] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/src/streamlink/logger.py0000644000175100001660000002034215003227510020062 0ustar00runnerdockerfrom __future__ import annotations import logging import sys import warnings from collections.abc import Iterator from logging import CRITICAL, DEBUG, ERROR, INFO, WARNING from pathlib import Path from sys import version_info from threading import Lock from typing import IO, TYPE_CHECKING, Literal # noinspection PyProtectedMember from warnings import WarningMessage from streamlink.exceptions import StreamlinkWarning from streamlink.utils.times import fromlocaltimestamp if TYPE_CHECKING: _BaseLoggerClass = logging.Logger else: _BaseLoggerClass = logging.getLoggerClass() class StreamlinkLogger(_BaseLoggerClass): def iter(self, level: int, messages: Iterator[str], *args, **kwargs) -> Iterator[str]: """ Iterator wrapper for logging multiple items in a single call and checking log level only once """ if not self.isEnabledFor(level): yield from messages for message in messages: self._log(level, message, args, **kwargs) yield message FORMAT_STYLE: Literal["%", "{", "$"] = "{" FORMAT_BASE = "[{name}][{levelname}] {message}" FORMAT_DATE = "%H:%M:%S" REMOVE_BASE = ["streamlink", "streamlink_cli"] # Make NONE ("none") the highest possible level that suppresses all log messages: # `logging.NOTSET` (equal to 0) can't be used as the "none" level because of `logging.Logger.getEffectiveLevel()`, which # loops through the logger instance's ancestor chain and checks whether the instance's level is NOTSET. If it is NOTSET, # then it continues with the parent logger, which means that if the level of `streamlink.logger.root` was set to "none" and # its value NOTSET, then it would continue with `logging.root` whose default level is `logging.WARNING` (equal to 30). NONE = sys.maxsize # Add "trace" and "all" to Streamlink's log levels TRACE = 5 ALL = 2 # Define Streamlink's log levels (and register both lowercase and uppercase names) _levelToNames = { NONE: "none", CRITICAL: "critical", ERROR: "error", WARNING: "warning", INFO: "info", DEBUG: "debug", TRACE: "trace", ALL: "all", } _custom_levels = TRACE, ALL def _logmethodfactory(level: int, name: str): # fix module name that gets read from the call stack in the logging module # https://github.com/python/cpython/commit/5ca6d7469be53960843df39bb900e9c3359f127f if version_info >= (3, 11): def method(self, message, *args, **kws): if self.isEnabledFor(level): # increase the stacklevel by one and skip the `trace()` call here kws["stacklevel"] = 2 self._log(level, message, args, **kws) else: def method(self, message, *args, **kws): if self.isEnabledFor(level): self._log(level, message, args, **kws) method.__name__ = name return method for _level, _name in _levelToNames.items(): logging.addLevelName(_level, _name.upper()) logging.addLevelName(_level, _name) if _level in _custom_levels: setattr(StreamlinkLogger, _name, _logmethodfactory(_level, _name)) _config_lock = Lock() class StringFormatter(logging.Formatter): def __init__(self, *args, remove_base: list[str] | None = None, **kwargs): super().__init__(*args, **kwargs) self._remove_base = remove_base or [] self._usesTime = super().usesTime() # Validate the format's fields rec = logging.LogRecord("", 1, "", 1, "", None, None) super().format(rec) def usesTime(self): return self._usesTime def formatTime(self, record, datefmt=None): tdt = fromlocaltimestamp(record.created) return tdt.strftime(datefmt or self.default_time_format) def format(self, record): for rbase in self._remove_base: record.name = record.name.replace(f"{rbase}.", "") record.levelname = record.levelname.lower() return super().format(record) class StreamHandler(logging.StreamHandler): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._stream_reconfigure() def flush(self): try: super().flush() except OSError: # Python doesn't raise BrokenPipeError on Windows pass def setStream(self, stream): res = super().setStream(stream) if res: # pragma: no branch self._stream_reconfigure() return res def _stream_reconfigure(self): # make stream write calls escape unsupported characters (stdout/stderr encoding is not guaranteed to be utf-8) self.stream.reconfigure(errors="backslashreplace") class WarningLogRecord(logging.LogRecord): msg: WarningMessage # type: ignore[assignment] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.name = "warnings" self.levelname = self.msg.category.__name__ if self.msg.category else UserWarning.__name__ self.pathname = self.msg.filename self._path = Path(self.pathname) self.filename = self._path.name self.module = self._path.stem self.lineno = self.msg.lineno def getMessage(self) -> str: if self.msg.category and issubclass(self.msg.category, StreamlinkWarning): return f"{self.msg.message}" return f"{self.msg.message}\n {self.pathname}:{self.lineno}" def _log_record_factory(name, level, fn, lno, msg, args, exc_info, func=None, sinfo=None, **kwargs): if isinstance(msg, WarningMessage): # noinspection PyTypeChecker return WarningLogRecord(name, level, fn, lno, msg, args, exc_info, func, sinfo) return _log_record_factory_default(name, level, fn, lno, msg, args, exc_info, func=None, sinfo=None, **kwargs) # borrowed from stdlib and modified, so that `WarningMessage` gets passed as `msg` to the `WarningLogRecord` def _showwarning(message, category, filename, lineno, file=None, line=None): if file is not None: # pragma: no cover if _showwarning_default is not None: # noinspection PyCallingNonCallable _showwarning_default(message, category, filename, lineno, file, line) return warning = WarningMessage(message, category, filename, lineno, None, line) root.log(WARNING, warning, stacklevel=2) def capturewarnings(capture=False): global _showwarning_default # noqa: PLW0603 if capture: if _showwarning_default is None: _showwarning_default = warnings.showwarning warnings.showwarning = _showwarning else: if _showwarning_default is not None: warnings.showwarning = _showwarning_default _showwarning_default = None # noinspection PyShadowingBuiltins,PyPep8Naming def basicConfig( *, filename: str | Path | None = None, filemode: str = "a", format: str = FORMAT_BASE, # noqa: A002 datefmt: str = FORMAT_DATE, style: Literal["%", "{", "$"] = FORMAT_STYLE, level: str | None = None, stream: IO | None = None, remove_base: list[str] | None = None, capture_warnings: bool = False, ) -> logging.StreamHandler | None: with _config_lock: handler: logging.StreamHandler | None = None if filename is not None: handler = logging.FileHandler(filename, filemode, encoding="utf-8") elif stream is not None: handler = StreamHandler(stream) if handler is not None: formatter = StringFormatter( fmt=format, datefmt=datefmt, style=style, remove_base=remove_base or REMOVE_BASE, ) handler.setFormatter(formatter) root.addHandler(handler) if level is not None: root.setLevel(level) if capture_warnings: capturewarnings(True) return handler _showwarning_default = None _log_record_factory_default = logging.getLogRecordFactory() logging.setLogRecordFactory(_log_record_factory) logging.setLoggerClass(StreamlinkLogger) root = logging.getLogger("streamlink") root.setLevel(WARNING) levels = list(_levelToNames.values()) __all__ = [ "NONE", "CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG", "TRACE", "ALL", "StreamlinkLogger", "basicConfig", "root", "levels", "capturewarnings", ] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/src/streamlink/options.py0000644000175100001660000002572315003227510020306 0ustar00runnerdockerfrom __future__ import annotations import argparse from collections.abc import Callable, Iterable, Iterator, Mapping from typing import Any, ClassVar, Dict, Literal, TypeVar class Options(Dict[str, Any]): """ For storing options to be used by the Streamlink session and plugins, with default values. Note: Option names are normalized by replacing "_" with "-". This means that the keys ``example_one`` and ``example-one`` are equivalent. """ #: Optional getter mapping for :class:`Options` subclasses _MAP_GETTERS: ClassVar[Mapping[str, Callable[[Any, str], Any]]] = {} #: Optional setter mapping for :class:`Options` subclasses _MAP_SETTERS: ClassVar[Mapping[str, Callable[[Any, str, Any], None]]] = {} def __init__(self, defaults: Mapping[str, Any] | None = None): super().__init__() self._defaults = self._normalize_dict(defaults or {}) super().update(self._defaults) @staticmethod def _normalize_key(name: str) -> str: return name.replace("_", "-") @classmethod def _normalize_dict(cls, src: Mapping[str, Any]) -> dict[str, Any]: normalize_key = cls._normalize_key return {normalize_key(key): value for key, value in src.items()} @property def defaults(self): return self._defaults def clear(self) -> None: """Restore default options""" super().clear() self.update(self._defaults) def get(self, key: str) -> Any: # type: ignore[override] """Get the stored value of a specific key""" normalized = self._normalize_key(key) method = self._MAP_GETTERS.get(normalized) if method is not None: return method(self, normalized) else: return super().get(normalized) def get_explicit(self, key: str) -> Any: """Get the stored value of a specific key and ignore any get-mappings""" normalized = self._normalize_key(key) return super().get(normalized) def set(self, key: str, value: Any) -> None: """Set the value for a specific key""" normalized = self._normalize_key(key) method = self._MAP_SETTERS.get(normalized) if method is not None: method(self, normalized, value) else: super().__setitem__(normalized, value) def set_explicit(self, key: str, value: Any) -> None: """Set the value for a specific key and ignore any set-mappings""" normalized = self._normalize_key(key) super().__setitem__(normalized, value) # noinspection PyMethodOverriding def update(self, options: Mapping[str, Any]) -> None: # type: ignore[override] """Merge options""" for key, value in options.items(): self.set(key, value) def __getitem__(self, item): return self.get(item) def __setitem__(self, item, value): return self.set(item, value) _TChoices = TypeVar("_TChoices", bound=Iterable) class Argument: # noinspection PyShadowingBuiltins def __init__( self, name: str, # `ArgumentParser.add_argument()` keywords action: str | None = None, nargs: int | Literal["?", "*", "+"] | None = None, const: Any = None, default: Any = None, type: Callable[[Any], _TChoices | Any] | None = None, # noqa: A002 choices: _TChoices | None = None, required: bool = False, help: str | None = None, # noqa: A002 metavar: str | list[str] | tuple[str, ...] | None = None, dest: str | None = None, # additional `Argument()` keywords requires: str | list[str] | tuple[str, ...] | None = None, prompt: str | None = None, sensitive: bool = False, argument_name: str | None = None, ): """ Accepts most of the parameters accepted by :meth:`argparse.ArgumentParser.add_argument()`, except that - ``name`` is the name relative to the plugin name (can be overridden by ``argument_name``) and that only one argument name is supported - ``action`` must be a string and can't be a custom :class:`Action ` - ``required`` is a special case which is only enforced if the plugin is in use This class should not be instantiated directly. See the :func:`pluginargument ` decorator for adding custom plugin arguments. :param name: Argument name, without leading ``--`` or plugin name prefixes, e.g. ``"username"``, ``"password"``, etc. :param action: See :meth:`ArgumentParser.add_argument()` :param nargs: See :meth:`ArgumentParser.add_argument()` :param const: See :meth:`ArgumentParser.add_argument()` :param default: See :meth:`ArgumentParser.add_argument()` :param type: See :meth:`ArgumentParser.add_argument()` :param choices: See :meth:`ArgumentParser.add_argument()` :param required: See :meth:`ArgumentParser.add_argument()` :param help: See :meth:`ArgumentParser.add_argument()` :param metavar: See :meth:`ArgumentParser.add_argument()` :param dest: See :meth:`ArgumentParser.add_argument()` :param requires: List of other arguments which this argument requires, e.g. ``["password"]`` :param prompt: If the argument is required and not set, then this prompt message will be shown instead :param sensitive: Whether the argument is sensitive and should be masked (passwords, etc.) :param argument_name: Custom CLI argument name without the automatically added plugin name prefix """ self.name = name self.action = action self.nargs = nargs self.const = const self.type = type self.choices: tuple[Any, ...] | None = tuple(choices) if choices else None self.required = required # argparse compares the object identity of argparse.SUPPRESS self.help = argparse.SUPPRESS if help == argparse.SUPPRESS else help self.metavar: str | tuple[str, ...] | None = ( tuple(metavar) if metavar is not None and not isinstance(metavar, str) else metavar ) # fmt: skip self._default = default self._dest = self._normalize_dest(dest) if dest else None self.requires: tuple[str, ...] = ( tuple(requires) if requires is not None and not isinstance(requires, str) else ((requires,) if requires is not None else ()) ) self.prompt = prompt self.sensitive = sensitive self._argument_name = self._normalize_name(argument_name) if argument_name else None # special cases for storing the default value to check whether a plugin argument was set or not if action == "store_true": self.const = True self._default = False if default is None else default elif action == "store_false": self.const = False self._default = True if default is None else default @staticmethod def _normalize_name(name: str) -> str: return name.replace("_", "-").strip("-") @staticmethod def _normalize_dest(name: str) -> str: return name.replace("-", "_") def _name(self, plugin): return self._argument_name or self._normalize_name(f"{plugin}-{self.name}") def argument_name(self, plugin): return f"--{self._name(plugin)}" def namespace_dest(self, plugin): return self._normalize_dest(self._name(plugin)) @property def dest(self) -> str: return self._dest or self._normalize_dest(self.name) @property def default(self): # read-only return self._default # `ArgumentParser.add_argument()` keywords, except `name_or_flags` and `required` _ARGPARSE_ARGUMENT_KEYWORDS: ClassVar[Mapping[str, str]] = { "action": "action", "nargs": "nargs", "const": "const", "default": "default", "type": "type", "choices": "choices", "help": "help", "metavar": "metavar", "dest": "_dest", } @property def options(self) -> Mapping[str, Any]: return { name: getattr(self, attr) for name, attr in self._ARGPARSE_ARGUMENT_KEYWORDS.items() # don't pass keywords with ``None`` values to ``ArgumentParser.add_argument()`` if ( getattr(self, attr) is not None # don't include the const option value if the action is store_true or store_false and not (name == "const" and self.action in ("store_true", "store_false")) ) } def __hash__(self): return hash(( self.name, self.action, self.nargs, self.const, self.type, self.choices, self.required, self.help, self.metavar, self._default, self._dest, self.requires, self.prompt, self.sensitive, self._argument_name, )) def __eq__(self, other): return isinstance(other, self.__class__) and hash(self) == hash(other) class Arguments(Dict[str, Argument]): """ A collection of :class:`Argument` instances for :class:`Plugin ` classes. Should not be called directly, see the :func:`pluginargument ` decorator. """ def __init__(self, *args): # keep the initial arguments of the constructor in reverse order (see __iter__()) super().__init__({arg.name: arg for arg in reversed(args)}) def __iter__(self) -> Iterator[Argument]: # type: ignore[override] # iterate in reverse order due to add() being called by multiple pluginargument decorators in reverse order return reversed(self.values()) def __hash__(self): return hash(tuple(self.items())) def __eq__(self, other): return isinstance(other, self.__class__) and hash(self) == hash(other) def __ne__(self, other): return not self.__eq__(other) def add(self, argument: Argument) -> None: self[argument.name] = argument def requires(self, name: str) -> Iterator[Argument]: """ Find all :class:`Argument` instances required by name """ results = {name} argument = self.get(name) for reqname in argument.requires if argument else []: required = self.get(reqname) if not required: raise KeyError(f"{reqname} is not a valid argument for this plugin") if required.name in results: raise RuntimeError("cycle detected in plugin argument config") results.add(required.name) yield required for r in self.requires(required.name): if r.name in results: raise RuntimeError("cycle detected in plugin argument config") results.add(r.name) yield r __all__ = [ "Argument", "Arguments", "Options", ] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745694559.9508185 streamlink-7.3.0/src/streamlink/packages/0000755000175100001660000000000015003227540020011 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/src/streamlink/packages/__init__.py0000644000175100001660000000000015003227510022105 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/src/streamlink/packages/requests_file.py0000644000175100001660000001406215003227510023235 0ustar00runnerdocker""" Copyright 2015 Red Hat, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ import errno import io import locale import os import os.path import stat import sys from io import BytesIO from urllib.parse import unquote, urljoin, urlparse from requests import Response, codes from requests.adapters import BaseAdapter from streamlink.compat import is_win32 class FileAdapter(BaseAdapter): def send(self, request, **kwargs): """ Wraps a file, described in request, in a Response object. :param request: The PreparedRequest` being "sent". :returns: a Response object containing the file """ # Check that the method makes sense. Only support GET if request.method not in ("GET", "HEAD"): raise ValueError(f"Invalid request method {request.method}") # Parse the URL url_parts = urlparse(request.url) # Make the Windows URLs slightly nicer if is_win32 and url_parts.netloc.endswith(":"): url_parts = url_parts._replace(path=f"/{url_parts.netloc}{url_parts.path}", netloc="") # Reject URLs with a hostname component if url_parts.netloc and url_parts.netloc not in ("localhost", ".", "..", "-"): raise ValueError("file: URLs with hostname components are not permitted") # If the path is relative update it to be absolute if url_parts.netloc in (".", ".."): pwd = os.path.abspath(url_parts.netloc).replace(os.sep, "/") + "/" if is_win32: # prefix the path with a / in Windows pwd = f"/{pwd}" url_parts = url_parts._replace(path=urljoin(pwd, url_parts.path.lstrip("/"))) resp = Response() resp.url = request.url # Open the file, translate certain errors into HTTP responses # Use urllib's unquote to translate percent escapes into whatever # they actually need to be try: # If the netloc is - then read from stdin if url_parts.netloc == "-": resp.raw = sys.stdin.buffer # make a fake response URL, the current directory resp.url = "file://" + os.path.abspath(".").replace(os.sep, "/") + "/" else: # Split the path on / (the URL directory separator) and decode any # % escapes in the parts path_parts = [unquote(p) for p in url_parts.path.split('/')] # Strip out the leading empty parts created from the leading /'s while path_parts and not path_parts[0]: path_parts.pop(0) # If os.sep is in any of the parts, someone fed us some shenanigans. # Treat is like a missing file. if any(os.sep in p for p in path_parts): raise IOError(errno.ENOENT, os.strerror(errno.ENOENT)) # Look for a drive component. If one is present, store it separately # so that a directory separator can correctly be added to the real # path, and remove any empty path parts between the drive and the path. # Assume that a part ending with : or | (legacy) is a drive. if path_parts and (path_parts[0].endswith('|') or path_parts[0].endswith(':')): path_drive = path_parts.pop(0) if path_drive.endswith('|'): path_drive = f"{path_drive[:-1]}:" while path_parts and not path_parts[0]: path_parts.pop(0) else: path_drive = '' # Try to put the path back together # Join the drive back in, and stick os.sep in front of the path to # make it absolute. path = path_drive + os.sep + os.path.join(*path_parts) # Check if the drive assumptions above were correct. If path_drive # is set, and os.path.splitdrive does not return a drive, it wasn't # reall a drive. Put the path together again treating path_drive # as a normal path component. if path_drive and not os.path.splitdrive(path): path = os.sep + os.path.join(path_drive, *path_parts) # Use io.open since we need to add a release_conn method, and # methods can't be added to file objects in python 2. resp.raw = io.open(path, "rb") resp.raw.release_conn = resp.raw.close except IOError as e: if e.errno == errno.EACCES: resp.status_code = codes.forbidden elif e.errno == errno.ENOENT: resp.status_code = codes.not_found else: resp.status_code = codes.bad_request # Wrap the error message in a file-like object # The error message will be localized, try to convert the string # representation of the exception into a byte stream resp_str = str(e).encode(locale.getpreferredencoding(False)) resp.raw = BytesIO(resp_str) resp.headers['Content-Length'] = len(resp_str) # Add release_conn to the BytesIO object resp.raw.release_conn = resp.raw.close else: resp.status_code = codes.ok # If it's a regular file, set the Content-Length resp_stat = os.fstat(resp.raw.fileno()) if stat.S_ISREG(resp_stat.st_mode): resp.headers['Content-Length'] = resp_stat.st_size return resp def close(self): pass ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745694559.9508185 streamlink-7.3.0/src/streamlink/plugin/0000755000175100001660000000000015003227540017531 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/src/streamlink/plugin/__init__.py0000644000175100001660000000107015003227510021635 0ustar00runnerdockerfrom streamlink.exceptions import PluginError from streamlink.options import Argument as PluginArgument, Arguments as PluginArguments, Options as PluginOptions from streamlink.plugin.plugin import ( HIGH_PRIORITY, LOW_PRIORITY, NO_PRIORITY, NORMAL_PRIORITY, Plugin, pluginargument, pluginmatcher, ) __all__ = [ "HIGH_PRIORITY", "NORMAL_PRIORITY", "LOW_PRIORITY", "NO_PRIORITY", "Plugin", "PluginArguments", "PluginArgument", "PluginError", "PluginOptions", "pluginmatcher", "pluginargument", ] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745694559.9518187 streamlink-7.3.0/src/streamlink/plugin/api/0000755000175100001660000000000015003227540020302 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/src/streamlink/plugin/api/__init__.py0000644000175100001660000000000015003227510022376 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/src/streamlink/plugin/api/useragents.py0000644000175100001660000000014515003227510023031 0ustar00runnerdocker# noinspection PyUnresolvedReferences from streamlink.session.http_useragents import * # noqa: F403 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/src/streamlink/plugin/api/validate.py0000644000175100001660000000012615003227510022441 0ustar00runnerdocker# noinspection PyUnresolvedReferences from streamlink.validate import * # noqa: F403 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1745694559.9518187 streamlink-7.3.0/src/streamlink/plugin/api/webbrowser/0000755000175100001660000000000015003227540022463 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/src/streamlink/plugin/api/webbrowser/__init__.py0000644000175100001660000000023715003227510024573 0ustar00runnerdocker""" Similar to the APIs of the ``streamlink.webbrowser`` package, this subpackage is considered unstable and may change at any time. Use at your own risk! """ ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/src/streamlink/plugin/api/webbrowser/aws_waf.py0000644000175100001660000000573515003227510024473 0ustar00runnerdockerfrom __future__ import annotations import logging import time from urllib.parse import urlparse import trio from requests.cookies import RequestsCookieJar from streamlink.compat import BaseExceptionGroup from streamlink.session import Streamlink from streamlink.webbrowser.cdp import CDPClient, CDPClientSession, devtools log = logging.getLogger(__name__) class AWSWAF: """ Solves the AWS Web Application Firewall challenge in a locally spawned web browser. Headless mode is detected by AWS. """ HOSTNAME = ".token.awswaf.com" TOKEN = "aws-waf-token" EXPIRATION = 3600 * 24 * 4 def __init__(self, session: Streamlink): self.session = session def acquire(self, url: str) -> bool: send: trio.MemorySendChannel[str | None] receive: trio.MemoryReceiveChannel[str | None] data = None send, receive = trio.open_memory_channel(1) timeout = self.session.get_option("webbrowser-timeout") async def on_request(client_session: CDPClientSession, request: devtools.fetch.RequestPaused): cookies = request.request.headers.get("Cookie", "") cookie = next((cookie for cookie in cookies.split(";") if cookie.startswith(f"{self.TOKEN}=")), None) req_url = request.request.url hostname = urlparse(req_url).hostname # pass through all requests if the cookie wasn't set yet and the request URL is the initial one or an AWS one if cookie is None and (req_url == url or hostname and hostname.endswith(self.HOSTNAME)): return await client_session.continue_request(request) # return cookie once found if cookie is not None: await send.send(cookie) # block all unneeded requests return await client_session.fulfill_request(request, body="") async def acquire_token(client: CDPClient): client_session: CDPClientSession async with client.session(max_buffer_size=100) as client_session: client_session.add_request_handler(on_request, on_request=True) with trio.move_on_after(timeout): async with client_session.navigate(url) as frame_id: await client_session.loaded(frame_id) return await receive.receive() try: data = CDPClient.launch(self.session, acquire_token) except BaseExceptionGroup: log.exception("Failed acquiring AWS WAF token") except Exception as err: log.error(err) if not data: log.error("No AWS WAF token has been acquired") return False domain = urlparse(url).hostname cookiejar = RequestsCookieJar() cookiejar.set( *data.split("=", 1), domain="" if not domain else f".{domain}", expires=time.time() + self.EXPIRATION, ) self.session.http.cookies.update(cookiejar) return True ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/src/streamlink/plugin/api/websocket.py0000644000175100001660000001460215003227510022642 0ustar00runnerdockerfrom __future__ import annotations import json import logging from threading import RLock, Thread, current_thread from typing import Any from urllib.parse import unquote_plus, urlparse from certifi import where as certify_where from websocket import ABNF, STATUS_NORMAL, WebSocketApp, enableTrace # type: ignore[attr-defined,import] from streamlink.logger import TRACE, root as rootlogger from streamlink.session import Streamlink log = logging.getLogger(__name__) class WebsocketClient(Thread): OPCODE_CONT: int = ABNF.OPCODE_CONT OPCODE_TEXT: int = ABNF.OPCODE_TEXT OPCODE_BINARY: int = ABNF.OPCODE_BINARY OPCODE_CLOSE: int = ABNF.OPCODE_CLOSE OPCODE_PING: int = ABNF.OPCODE_PING OPCODE_PONG: int = ABNF.OPCODE_PONG _id: int = 0 ws: WebSocketApp def __init__( self, session: Streamlink, url: str, subprotocols: list[str] | None = None, header: list[str] | dict[str, str] | None = None, cookie: str | None = None, sockopt: tuple | None = None, sslopt: dict | None = None, host: str | None = None, origin: str | None = None, suppress_origin: bool = False, ping_interval: int | float = 0, ping_timeout: int | float | None = None, ping_payload: str = "", ): if rootlogger.level <= TRACE: enableTrace(True, handler=next(iter(rootlogger.handlers), logging.StreamHandler())) # type: ignore if not header: header = [] elif isinstance(header, dict): header = [f"{k!s}: {v!s}" for k, v in header.items()] if not any(True for h in header if h.startswith("User-Agent: ")): header.append(f"User-Agent: {session.http.headers['User-Agent']!s}") proxy_options: dict[str, Any] = {} http_proxy: str | None = session.get_option("http-proxy") if http_proxy: p = urlparse(http_proxy) proxy_options["proxy_type"] = p.scheme proxy_options["http_proxy_host"] = p.hostname if p.port: # pragma: no branch proxy_options["http_proxy_port"] = p.port if p.username: # pragma: no branch proxy_options["http_proxy_auth"] = unquote_plus(p.username), unquote_plus(p.password or "") self._reconnect = False self._reconnect_lock = RLock() if not sslopt: # pragma: no cover sslopt = {} sslopt.setdefault("ca_certs", certify_where()) self.session = session self._ws_init(url, subprotocols, header, cookie) self._ws_rundata = dict( sockopt=sockopt, sslopt=sslopt, host=host, origin=origin, suppress_origin=suppress_origin, ping_interval=ping_interval, ping_timeout=ping_timeout, ping_payload=ping_payload, **proxy_options, ) self._id += 1 super().__init__( name=f"Thread-{self.__class__.__name__}-{self._id}", daemon=True, ) def _ws_init(self, url, subprotocols, header, cookie): self.ws = WebSocketApp( url=url, subprotocols=subprotocols, header=header, cookie=cookie, on_open=self.on_open, on_error=self.on_error, on_close=self.on_close, on_ping=self.on_ping, on_pong=self.on_pong, on_message=self.on_message, on_cont_message=self.on_cont_message, on_data=self.on_data, ) def run(self) -> None: while True: log.debug(f"Connecting to: {self.ws.url}") self.ws.run_forever(**self._ws_rundata) # check if closed via a reconnect() call with self._reconnect_lock: if not self._reconnect: return self._reconnect = False # ---- def reconnect( self, url: str | None = None, subprotocols: list[str] | None = None, header: list | dict | None = None, cookie: str | None = None, closeopts: dict | None = None, ) -> None: with self._reconnect_lock: # ws connection is not active (anymore) if not self.ws.keep_running: return log.debug("Reconnecting...") self._reconnect = True self.ws.close(**(closeopts or {})) self._ws_init( url=self.ws.url if url is None else url, subprotocols=self.ws.subprotocols if subprotocols is None else subprotocols, header=self.ws.header if header is None else header, cookie=self.ws.cookie if cookie is None else cookie, ) def close(self, status: int = STATUS_NORMAL, reason: str | bytes = "", timeout: int = 3) -> None: if isinstance(reason, str): reason = bytes(reason, encoding="utf-8") self.ws.close(status=status, reason=reason, timeout=timeout) if self.is_alive() and current_thread() is not self: self.join() def send(self, data: str | bytes, opcode: int = ABNF.OPCODE_TEXT) -> None: return self.ws.send(data, opcode) def send_json(self, data: Any) -> None: return self.send(json.dumps(data, indent=None, separators=(",", ":"))) # ---- # noinspection PyMethodMayBeStatic def on_open(self, wsapp: WebSocketApp) -> None: log.debug(f"Connected: {wsapp.url}") # pragma: no cover # noinspection PyMethodMayBeStatic # noinspection PyUnusedLocal def on_error(self, wsapp: WebSocketApp, error: Exception) -> None: log.error(error) # pragma: no cover # noinspection PyMethodMayBeStatic # noinspection PyUnusedLocal def on_close(self, wsapp: WebSocketApp, status: int, message: str) -> None: log.debug(f"Closed: {wsapp.url}") # pragma: no cover def on_ping(self, wsapp: WebSocketApp, data: bytes) -> None: pass # pragma: no cover def on_pong(self, wsapp: WebSocketApp, data: bytes) -> None: pass # pragma: no cover def on_message(self, wsapp: WebSocketApp, data: str) -> None: pass # pragma: no cover def on_cont_message(self, wsapp: WebSocketApp, data: bytes, cont: Any) -> None: pass # pragma: no cover def on_data(self, wsapp: WebSocketApp, data: bytes | str, data_type: int, cont: Any) -> None: pass # pragma: no cover ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/src/streamlink/plugin/plugin.py0000644000175100001660000006367315003227510021415 0ustar00runnerdockerfrom __future__ import annotations import ast import logging import operator import re import time from collections.abc import Callable, Iterable, Mapping from contextlib import suppress from functools import partial from http.cookiejar import Cookie from typing import TYPE_CHECKING, Any, ClassVar, List, Literal, NamedTuple, Type, TypeVar, Union import requests.cookies import streamlink.utils.args import streamlink.utils.times from streamlink.cache import Cache from streamlink.exceptions import FatalPluginError, NoStreamsError, PluginError from streamlink.options import Argument, Arguments, Options from streamlink.user_input import UserInputRequester if TYPE_CHECKING: # pragma: no cover from streamlink.session.session import Streamlink #: See the :func:`~.pluginargument` decorator _PLUGINARGUMENT_TYPE_REGISTRY: Mapping[str, Callable[[Any], Any]] = { "int": int, "float": float, "bool": streamlink.utils.args.boolean, "comma_list": streamlink.utils.args.comma_list, "comma_list_filter": streamlink.utils.args.comma_list_filter, "filesize": streamlink.utils.args.filesize, "keyvalue": streamlink.utils.args.keyvalue, "num": streamlink.utils.args.num, "hours_minutes_seconds": streamlink.utils.times.hours_minutes_seconds, "hours_minutes_seconds_float": streamlink.utils.times.hours_minutes_seconds_float, } log = logging.getLogger(__name__) # FIXME: This is a crude attempt at making a bitrate's # weight end up similar to the weight of a resolution. # Someone who knows math, please fix. BIT_RATE_WEIGHT_RATIO = 2.8 ALT_WEIGHT_MOD = 0.01 QUALITY_WEIGHTS_EXTRA = { "other": { "live": 1080, }, "tv": { "hd": 1080, "sd": 576, }, "quality": { "ehq": 720, "hq": 576, "sq": 360, }, } FILTER_OPERATORS = { "<": operator.lt, "<=": operator.le, ">": operator.gt, ">=": operator.ge, } PARAMS_REGEX = r"(\w+)=({.+?}|\[.+?\]|\(.+?\)|'(?:[^'\\]|\\')*'|\"(?:[^\"\\]|\\\")*\"|\S+)" HIGH_PRIORITY = 30 NORMAL_PRIORITY = 20 LOW_PRIORITY = 10 NO_PRIORITY = 0 _COOKIE_KEYS = ( "version", "name", "value", "port", "domain", "path", "secure", "expires", "discard", "comment", "comment_url", "rfc2109", ) def stream_weight(stream): for group, weights in QUALITY_WEIGHTS_EXTRA.items(): if stream in weights: return weights[stream], group match = re.match(r"^(\d+)(k|p)?(\d+)?(\+)?(?:[a_](\d+)k)?(?:_(alt)(\d)?)?$", stream) if match: weight = 0 if match.group(6): if match.group(7): weight -= ALT_WEIGHT_MOD * int(match.group(7)) else: weight -= ALT_WEIGHT_MOD name_type = match.group(2) if name_type == "k": # bit rate bitrate = int(match.group(1)) weight += bitrate / BIT_RATE_WEIGHT_RATIO return weight, "bitrate" elif name_type == "p": # resolution weight += int(match.group(1)) if match.group(3): # fps eg. 60p or 50p weight += int(match.group(3)) if match.group(4) == "+": weight += 1 if match.group(5): # bit rate classifier for resolution weight += int(match.group(5)) / BIT_RATE_WEIGHT_RATIO return weight, "pixels" return 0, "none" def iterate_streams(streams): for name, stream in streams: if isinstance(stream, list): for sub_stream in stream: yield name, sub_stream else: yield name, stream def stream_type_priority(stream_types, stream): stream_type = type(stream[1]).shortname() try: prio = stream_types.index(stream_type) except ValueError: try: prio = stream_types.index("*") except ValueError: prio = 99 return prio def stream_sorting_filter(expr, stream_weight): match = re.match(r"(?P<=|>=|<|>)?(?P[\w+]+)", expr) if not match: raise PluginError("Invalid filter expression: {0}".format(expr)) op, value = match.group("op", "value") op = FILTER_OPERATORS.get(op, operator.eq) filter_weight, filter_group = stream_weight(value) def func(quality): weight, group = stream_weight(quality) if group == filter_group: return not op(weight, filter_weight) return True return func def parse_params(params: str | None = None) -> dict[str, Any]: rval: dict[str, Any] = {} if not params: return rval matches = re.findall(PARAMS_REGEX, params) for key, value in matches: with suppress(Exception): value = ast.literal_eval(value) rval[key] = value return rval class Matcher(NamedTuple): pattern: re.Pattern priority: int name: str | None = None MType = TypeVar("MType") class _MCollection(List[MType]): def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self._names: dict[str, MType] = {} def __getitem__(self, item): return self._names[item] if isinstance(item, str) else super().__getitem__(item) class Matchers(_MCollection[Matcher]): def __init__(self, *matchers): super().__init__(matchers) for matcher in matchers: self._add_named_matcher(matcher) def add(self, matcher: Matcher) -> None: super().insert(0, matcher) self._add_named_matcher(matcher) def _add_named_matcher(self, matcher: Matcher) -> None: if matcher.name: if matcher.name in self._names: raise ValueError(f"A matcher named '{matcher.name}' has already been registered") self._names[matcher.name] = matcher class Matches(_MCollection[Union[re.Match, None]]): def update(self, matchers: Matchers, value: str) -> tuple[re.Pattern | None, re.Match | None]: matches = [(matcher, matcher.pattern.match(value)) for matcher in matchers] self.clear() self.extend(match for matcher, match in matches) self._names.clear() self._names.update((matcher.name, match) for matcher, match in matches if matcher.name) return next(((matcher.pattern, match) for matcher, match in matches if match is not None), (None, None)) class PluginMeta(type): def __init__(cls, name, bases, namespace, **kwargs): super().__init__(name, bases, namespace, **kwargs) cls.matchers = Matchers(*getattr(cls, "matchers", [])) cls.arguments = Arguments(*getattr(cls, "arguments", [])) class Plugin(metaclass=PluginMeta): """ Plugin base class for retrieving streams and metadata from the URL specified. """ #: The Streamlink session which this plugin instance belongs to, #: with access to its :attr:`HTTPSession `. session: Streamlink #: Plugin options, initialized with the user-set values of the plugin's arguments. options: Options #: Plugin cache object, used to store plugin-specific data other than HTTP session cookies. cache: Cache #: The list of plugin matchers (URL pattern + priority + optional name). #: Supports matcher lookups by the matcher index or the optional matcher name. #: #: Use the :func:`pluginmatcher` decorator to initialize plugin matchers. matchers: ClassVar[Matchers] #: The plugin's :class:`Arguments ` collection. #: #: Use the :func:`pluginargument` decorator to initialize plugin arguments. arguments: ClassVar[Arguments] #: A list of optional :class:`re.Match` results of all defined matchers. #: Supports match lookups by the matcher index or the optional matcher name. matches: Matches #: A reference to the compiled :class:`re.Pattern` of the first matching matcher. matcher: re.Pattern | None = None #: A reference to the :class:`re.Match` result of the first matching matcher. match: re.Match | None = None #: Metadata 'id' attribute: unique stream ID, etc. id: str | None = None #: Metadata 'title' attribute: the stream's short descriptive title. title: str | None = None #: Metadata 'author' attribute: the channel or broadcaster name, etc. author: str | None = None #: Metadata 'category' attribute: name of a game being played, a music genre, etc. category: str | None = None _url: str = "" def __init__(self, session: Streamlink, url: str, options: Mapping[str, Any] | Options | None = None): """ :param session: The Streamlink session instance :param url: The input URL used for finding and resolving streams :param options: An optional :class:`Options` instance """ modulename = self.__class__.__module__ self.module = modulename.split(".")[-1] self.logger = logging.getLogger(modulename) self.options = Options(options) self.cache = Cache( filename="plugin-cache.json", key_prefix=self.module, ) self.session: Streamlink = session self.matches = Matches() self.url: str = url if self.matchers and not self.match: raise PluginError("The input URL did not match any of this plugin's matchers") self.load_cookies() @property def url(self) -> str: """ The plugin's input URL. Setting a new value will automatically update the :attr:`matches`, :attr:`matcher` and :attr:`match` data. """ return self._url @url.setter def url(self, value: str): self._url = value if self.matchers: self.matcher, self.match = self.matches.update(self.matchers, value) def set_option(self, key, value): self.options.set(key, value) def get_option(self, key): return self.options.get(key) @classmethod def get_argument(cls, key): return cls.arguments and cls.arguments.get(key) @classmethod def stream_weight(cls, stream): return stream_weight(stream) @classmethod def default_stream_types(cls, streams): stream_types = ["hls", "http"] for _name, stream in iterate_streams(streams): stream_type = type(stream).shortname() if stream_type not in stream_types: stream_types.append(stream_type) return stream_types def streams(self, stream_types=None, sorting_excludes=None): """ Attempts to extract available streams. Returns a :class:`dict` containing the streams, where the key is the name of the stream (most commonly the quality name), with the value being a :class:`Stream ` instance. The result can contain the synonyms **best** and **worst** which point to the streams which are likely to be of highest and lowest quality respectively. If multiple streams with the same name are found, the order of streams specified in *stream_types* will determine which stream gets to keep the name while the rest will be renamed to "_". The synonyms can be fine-tuned with the *sorting_excludes* parameter, which can be one of these types: - A list of filter expressions in the format ``[operator]``. For example the filter ">480p" will exclude streams ranked higher than "480p" from the list used in the synonyms ranking. Valid operators are ``>``, ``>=``, ``<`` and ``<=``. If no operator is specified then equality will be tested. - A function that is passed to :meth:`filter` with a list of stream names as input. :param stream_types: A list of stream types to return :param sorting_excludes: Specify which streams to exclude from the best/worst synonyms :returns: A :class:`dict` of stream names and :class:`Stream ` instances """ try: ostreams = self._get_streams() if isinstance(ostreams, dict): ostreams = ostreams.items() # Flatten the iterator to a list so we can reuse it. if ostreams: ostreams = list(ostreams) except NoStreamsError: return {} except (OSError, ValueError) as err: raise PluginError(err) from err if not ostreams: return {} if stream_types is None: stream_types = self.default_stream_types(ostreams) # Add streams depending on stream type and priorities sorted_streams = sorted(iterate_streams(ostreams), key=partial(stream_type_priority, stream_types)) streams = {} for name, stream in sorted_streams: stream_type = type(stream).shortname() # Use * as wildcard to match other stream types if "*" not in stream_types and stream_type not in stream_types: continue # drop _alt from any stream names if name.endswith("_alt"): name = name[: -len("_alt")] existing = streams.get(name) if existing: existing_stream_type = type(existing).shortname() if existing_stream_type != stream_type: name = "{0}_{1}".format(name, stream_type) if name in streams: name = "{0}_alt".format(name) num_alts = len(list(filter(lambda n: n.startswith(name), streams.keys()))) # We shouldn't need more than 2 alt streams if num_alts >= 2: continue elif num_alts > 0: name = "{0}{1}".format(name, num_alts + 1) # Validate stream name and discard the stream if it's bad. match = re.match(r"([A-z0-9_+]+)", name) if match: name = match.group(1) else: self.logger.debug(f"The stream '{name}' has been ignored since it is badly named.") continue # Force lowercase name and replace space with underscore. streams[name.lower()] = stream # Create the best/worst synonyms def stream_weight_only(s): return self.stream_weight(s)[0] or (len(streams) == 1 and 1) stream_names = filter(stream_weight_only, streams.keys()) sorted_streams = sorted(stream_names, key=stream_weight_only) unfiltered_sorted_streams = sorted_streams if isinstance(sorting_excludes, list): for expr in sorting_excludes: filter_func = stream_sorting_filter(expr, self.stream_weight) sorted_streams = list(filter(filter_func, sorted_streams)) elif callable(sorting_excludes): sorted_streams = list(filter(sorting_excludes, sorted_streams)) final_sorted_streams = {} for stream_name in sorted(streams, key=stream_weight_only): final_sorted_streams[stream_name] = streams[stream_name] if len(sorted_streams) > 0: best = sorted_streams[-1] worst = sorted_streams[0] final_sorted_streams["worst"] = streams[worst] final_sorted_streams["best"] = streams[best] elif len(unfiltered_sorted_streams) > 0: best = unfiltered_sorted_streams[-1] worst = unfiltered_sorted_streams[0] final_sorted_streams["worst-unfiltered"] = streams[worst] final_sorted_streams["best-unfiltered"] = streams[best] return final_sorted_streams def _get_streams(self): """ Implement the stream and metadata retrieval here. Needs to return either a dict of :class:`Stream ` instances mapped by stream name, or needs to act as a generator which yields tuples of stream names and :class:`Stream ` instances. """ raise NotImplementedError def get_metadata(self) -> Mapping[str, str | None]: return dict( id=self.get_id(), author=self.get_author(), category=self.get_category(), title=self.get_title(), ) def get_id(self) -> str | None: return None if self.id is None else str(self.id).strip() def get_title(self) -> str | None: return None if self.title is None else str(self.title).strip() def get_author(self) -> str | None: return None if self.author is None else str(self.author).strip() def get_category(self) -> str | None: return None if self.category is None else str(self.category).strip() def save_cookies( self, cookie_filter: Callable[[Cookie], bool] | None = None, default_expires: int = 60 * 60 * 24 * 7, ) -> list[str]: """ Store the cookies from :attr:`session.http` in the plugin cache until they expire. The cookies can be filtered by supplying a filter method. e.g. ``lambda c: "auth" in c.name``. If no expiry date is given in the cookie then the ``default_expires`` value will be used. :param cookie_filter: a function to filter the cookies :param default_expires: time (in seconds) until cookies with no expiry will expire :return: list of the saved cookie names """ cookie_filter = cookie_filter or (lambda c: True) saved = [] for cookie in self.session.http.cookies: if not cookie_filter(cookie): continue cookie_dict = {} for key in _COOKIE_KEYS: cookie_dict[key] = getattr(cookie, key, None) cookie_dict["rest"] = getattr(cookie, "rest", getattr(cookie, "_rest", None)) expires = default_expires if cookie_dict["expires"]: expires = int(cookie_dict["expires"] - time.time()) key = "__cookie:{0}:{1}:{2}:{3}".format( cookie.name, cookie.domain, cookie.port_specified and cookie.port or "80", cookie.path_specified and cookie.path or "*", ) self.cache.set(key, cookie_dict, expires) saved.append(cookie.name) if saved: # pragma: no branch self.logger.debug(f"Saved cookies: {', '.join(saved)}") return saved def load_cookies(self) -> list[str]: """ Load any stored cookies for the plugin that have not expired. :return: list of the restored cookie names """ restored = [] for key, value in self.cache.get_all().items(): if key.startswith("__cookie"): cookie = requests.cookies.create_cookie(**value) self.session.http.cookies.set_cookie(cookie) restored.append(cookie.name) if restored: # pragma: no branch self.logger.debug(f"Restored cookies: {', '.join(restored)}") return restored def clear_cookies(self, cookie_filter: Callable[[Cookie], bool] | None = None) -> list[str]: """ Removes all saved cookies for this plugin. To filter the cookies that are deleted specify the ``cookie_filter`` argument (see :meth:`save_cookies`). :param cookie_filter: a function to filter the cookies :type cookie_filter: function :return: list of the removed cookie names """ cookie_filter = cookie_filter or (lambda c: True) removed = [] for key, value in sorted(self.cache.get_all().items(), key=operator.itemgetter(0), reverse=True): if key.startswith("__cookie"): cookie = requests.cookies.create_cookie(**value) if cookie_filter(cookie): del self.session.http.cookies[cookie.name] self.cache.set(key, None, 0) removed.append(key) return removed def input_ask(self, prompt: str) -> str: user_input_requester: UserInputRequester | None = self.session.get_option("user-input-requester") if user_input_requester: try: return user_input_requester.ask(prompt) except OSError as err: raise FatalPluginError(f"User input error: {err}") from err raise FatalPluginError("This plugin requires user input, however it is not supported on this platform") def input_ask_password(self, prompt: str) -> str: user_input_requester: UserInputRequester | None = self.session.get_option("user-input-requester") if user_input_requester: try: return user_input_requester.ask_password(prompt) except OSError as err: raise FatalPluginError(f"User input error: {err}") from err raise FatalPluginError("This plugin requires user input, however it is not supported on this platform") def pluginmatcher( pattern: re.Pattern, priority: int = NORMAL_PRIORITY, name: str | None = None, ) -> Callable[[type[Plugin]], type[Plugin]]: """ Decorator for plugin URL matchers. A matcher consists of a compiled regular expression pattern for the plugin's input URL, a priority value and an optional name. The priority value determines which plugin gets chosen by :meth:`Streamlink.resolve_url() ` if multiple plugins match the input URL. The matcher name can be used for accessing it and its matching result when multiple matchers are defined. Plugins must at least have one matcher. If multiple matchers are defined, then the first matching one according to the order of which they have been defined (top to bottom) will be responsible for setting the :attr:`Plugin.matcher` and :attr:`Plugin.match` attributes on the :class:`Plugin` instance. The :attr:`Plugin.matchers` and :attr:`Plugin.matches` attributes are affected by all defined matchers, and both support referencing matchers and matches by matcher index and name. .. code-block:: python import re from streamlink.plugin import HIGH_PRIORITY, Plugin, pluginmatcher @pluginmatcher(re.compile("https?://example:1234/(?:foo|bar)/(?P[^/]+)")) @pluginmatcher(priority=HIGH_PRIORITY, pattern=re.compile(\"\"\" https?://(?: sitenumberone |adifferentsite |somethingelse ) /.+\\.m3u8 \"\"\", re.VERBOSE)) class MyPlugin(Plugin): ... """ matcher = Matcher(pattern, priority, name) def decorator(cls: type[Plugin]) -> type[Plugin]: if not issubclass(cls, Plugin): raise TypeError(f"{cls.__name__} is not a Plugin") cls.matchers.add(matcher) return cls return decorator _TChoices = TypeVar("_TChoices", bound=Iterable) # noinspection GrazieInspection,PyShadowingBuiltins def pluginargument( name: str, action: str | None = None, nargs: int | Literal["?", "*", "+"] | None = None, const: Any = None, default: Any = None, type: str | Callable[[Any], _TChoices | Any] | None = None, # noqa: A002 type_args: list | tuple | None = None, type_kwargs: Mapping[str, Any] | None = None, choices: _TChoices | None = None, required: bool = False, help: str | None = None, # noqa: A002 metavar: str | list[str] | tuple[str, ...] | None = None, dest: str | None = None, requires: str | list[str] | tuple[str, ...] | None = None, prompt: str | None = None, sensitive: bool = False, argument_name: str | None = None, ) -> Callable[[type[Plugin]], type[Plugin]]: """ Decorator for plugin arguments. Takes the same arguments as :class:`Argument `. One exception is the ``type`` argument, which also accepts a ``str`` value: Plugins built into Streamlink **must** reference the used argument-type function by name, so the pluginargument data can be JSON-serialized. ``type_args`` and ``type_kwargs`` can be used to parametrize the type-argument function, but their values **must** only consist of literal objects. The available functions are defined in the :data:`~._PLUGINARGUMENT_TYPE_REGISTRY`. .. code-block:: python from streamlink.plugin import Plugin, pluginargument @pluginargument( "username", requires=["password"], metavar="EMAIL", help="The username for your account.", ) @pluginargument( "password", sensitive=True, metavar="PASSWORD", help="The password for your account.", ) class MyPlugin(Plugin): ... This will add the ``--myplugin-username`` and ``--myplugin-password`` arguments to the CLI, assuming the plugin's module name is ``myplugin``. """ argument_type: Callable[[Any], _TChoices] | None if not isinstance(type, str): argument_type = type else: if type not in _PLUGINARGUMENT_TYPE_REGISTRY: raise TypeError(f"Invalid pluginargument type {type}") argument_type = _PLUGINARGUMENT_TYPE_REGISTRY[type] if type_args is not None or type_kwargs is not None: argument_type = argument_type(*(type_args or ()), **(type_kwargs or {})) arg = Argument( name=name, action=action, nargs=nargs, const=const, default=default, type=argument_type, choices=choices, required=required, help=help, metavar=metavar, dest=dest, requires=requires, prompt=prompt, sensitive=sensitive, argument_name=argument_name, ) def decorator(cls: Type[Plugin]) -> Type[Plugin]: if not issubclass(cls, Plugin): raise TypeError(f"{repr(cls)} is not a Plugin") # noqa: RUF010 # builtins.repr gets monkeypatched in tests cls.arguments.add(arg) return cls return decorator __all__ = [ "HIGH_PRIORITY", "NORMAL_PRIORITY", "LOW_PRIORITY", "NO_PRIORITY", "Plugin", "Matcher", "pluginmatcher", "pluginargument", ] ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1745694559.974819 streamlink-7.3.0/src/streamlink/plugins/0000755000175100001660000000000015003227540017714 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/src/streamlink/plugins/__init__.py0000644000175100001660000000000015003227510022010 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/src/streamlink/plugins/abematv.py0000644000175100001660000002220115003227510021677 0ustar00runnerdocker""" $description Japanese live TV streaming website with multiple channels including news, sports, entertainment and anime. $url abema.tv $type live, vod $region Japan """ import hashlib import hmac import logging import re import struct import time import uuid from base64 import urlsafe_b64encode from binascii import unhexlify from requests import Response from requests.adapters import BaseAdapter from streamlink.exceptions import NoStreamsError from streamlink.plugin import Plugin, pluginmatcher from streamlink.plugin.api import useragents, validate from streamlink.stream.hls import HLSStream, HLSStreamReader, HLSStreamWriter from streamlink.utils.crypto import AES from streamlink.utils.url import update_qsd log = logging.getLogger(__name__) class AbemaTVHLSStreamWriter(HLSStreamWriter): def should_filter_segment(self, segment): return "/tsad/" in segment.uri or super().should_filter_segment(segment) class AbemaTVHLSStreamReader(HLSStreamReader): __writer__ = AbemaTVHLSStreamWriter class AbemaTVHLSStream(HLSStream): __reader__ = AbemaTVHLSStreamReader class AbemaTVLicenseAdapter(BaseAdapter): """ Handling abematv-license:// protocol to get real video key_data. """ STRTABLE = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" HKEY = b"3AF0298C219469522A313570E8583005A642E73EDD58E3EA2FB7339D3DF1597E" _MEDIATOKEN_API = "https://api.abema.io/v1/media/token" _LICENSE_API = "https://license.abema.io/abematv-hls" _MEDIATOKEN_SCHEMA = validate.Schema({"token": str}) _LICENSE_SCHEMA = validate.Schema({"k": str, "cid": str}) def __init__(self, session, deviceid, usertoken): self._session = session self.deviceid = deviceid self.usertoken = usertoken super().__init__() def _get_videokey_from_ticket(self, ticket): params = { "osName": "android", "osVersion": "6.0.1", "osLang": "ja_JP", "osTimezone": "Asia/Tokyo", "appId": "tv.abema", "appVersion": "3.27.1", } auth_header = {"Authorization": f"Bearer {self.usertoken}"} res = self._session.http.get(self._MEDIATOKEN_API, params=params, headers=auth_header) jsonres = self._session.http.json(res, schema=self._MEDIATOKEN_SCHEMA) mediatoken = jsonres["token"] res = self._session.http.post(self._LICENSE_API, params={"t": mediatoken}, json={"kv": "a", "lt": ticket}) jsonres = self._session.http.json(res, schema=self._LICENSE_SCHEMA) cid = jsonres["cid"] k = jsonres["k"] res = sum(self.STRTABLE.find(k[i]) * (58 ** (len(k) - 1 - i)) for i in range(len(k))) encvideokey = struct.pack(">QQ", res >> 64, res & 0xFFFFFFFFFFFFFFFF) # HKEY: # RC4KEY = unhexlify('DB98A8E7CECA3424D975280F90BD03EE') # RC4DATA = unhexlify(b'D4B718BBBA9CFB7D0192A58F9E2D146A' # b'FC5DB29E4352DE05FC4CF2C1005804BB') # rc4 = ARC4.new(RC4KEY) # HKEY = rc4.decrypt(RC4DATA) h = hmac.new(unhexlify(self.HKEY), (cid + self.deviceid).encode("utf-8"), digestmod=hashlib.sha256) enckey = h.digest() aes = AES.new(enckey, AES.MODE_ECB) return aes.decrypt(encvideokey) def send(self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None): resp = Response() resp.status_code = 200 ticket = re.findall(r"abematv-license://(.*)", request.url)[0] resp._content = self._get_videokey_from_ticket(ticket) return resp def close(self): return @pluginmatcher( name="onair", pattern=re.compile(r"https?://abema\.tv/now-on-air/(?P[^?]+)"), ) @pluginmatcher( name="episode", pattern=re.compile(r"https?://abema\.tv/video/episode/(?P[^?]+)"), ) @pluginmatcher( name="slots", pattern=re.compile(r"https?://abema\.tv/channels/.+?/slots/(?P[^?]+)"), ) class AbemaTV(Plugin): _CHANNEL = "https://api.abema.io/v1/channels" _USER_API = "https://api.abema.io/v1/users" _PRGM_API = "https://api.abema.io/v1/video/programs/{0}" _SLOTS_API = "https://api.abema.io/v1/media/slots/{0}" _PRGM3U8 = "https://vod-abematv.akamaized.net/program/{0}/playlist.m3u8" _SLOTM3U8 = "https://vod-abematv.akamaized.net/slot/{0}/playlist.m3u8" SECRETKEY = ( b"v+Gjs=25Aw5erR!J8ZuvRrCx*rGswhB&qdHd_SYerEWdU&a?3DzN9B" + b"Rbp5KwY4hEmcj5#fykMjJ=AuWz5GSMY-d@H7DMEh3M@9n2G552Us$$" + b"k9cD=3TxwWe86!x#Zyhe" ) _USER_SCHEMA = validate.Schema({"profile": {"userId": str}, "token": str}) _CHANNEL_SCHEMA = validate.Schema({ "channels": [ { "id": str, "name": str, "playback": { validate.optional("dash"): str, "hls": str, }, }, ], }) _PRGM_SCHEMA = validate.Schema({"terms": [{validate.optional("onDemandType"): int}]}) _SLOT_SCHEMA = validate.Schema({"slot": {"flags": {validate.optional("timeshiftFree"): bool}}}) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.session.http.headers.update({"User-Agent": useragents.CHROME}) def _generate_applicationkeysecret(self, deviceid): deviceid = deviceid.encode("utf-8") # for python3 # plus 1 hour and drop minute and secs # for python3 : floor division ts_1hour = (int(time.time()) + 60 * 60) // 3600 * 3600 time_struct = time.gmtime(ts_1hour) ts_1hour_str = str(ts_1hour).encode("utf-8") h = hmac.new(self.SECRETKEY, digestmod=hashlib.sha256) h.update(self.SECRETKEY) tmp = h.digest() for _ in range(time_struct.tm_mon): h = hmac.new(self.SECRETKEY, digestmod=hashlib.sha256) h.update(tmp) tmp = h.digest() h = hmac.new(self.SECRETKEY, digestmod=hashlib.sha256) h.update(urlsafe_b64encode(tmp).rstrip(b"=") + deviceid) tmp = h.digest() for _ in range(time_struct.tm_mday % 5): h = hmac.new(self.SECRETKEY, digestmod=hashlib.sha256) h.update(tmp) tmp = h.digest() h = hmac.new(self.SECRETKEY, digestmod=hashlib.sha256) h.update(urlsafe_b64encode(tmp).rstrip(b"=") + ts_1hour_str) tmp = h.digest() for _ in range(time_struct.tm_hour % 5): # utc hour h = hmac.new(self.SECRETKEY, digestmod=hashlib.sha256) h.update(tmp) tmp = h.digest() return urlsafe_b64encode(tmp).rstrip(b"=").decode("utf-8") def _is_playable(self, vtype, vid): auth_header = {"Authorization": f"Bearer {self.usertoken}"} if vtype == "episode": res = self.session.http.get(self._PRGM_API.format(vid), headers=auth_header) jsonres = self.session.http.json(res, schema=self._PRGM_SCHEMA) playable = False for item in jsonres["terms"]: if item.get("onDemandType", False) == 3: playable = True return playable elif vtype == "slots": res = self.session.http.get(self._SLOTS_API.format(vid), headers=auth_header) jsonres = self.session.http.json(res, schema=self._SLOT_SCHEMA) return jsonres["slot"]["flags"].get("timeshiftFree", False) is True def _get_streams(self): deviceid = str(uuid.uuid4()) appkeysecret = self._generate_applicationkeysecret(deviceid) json_data = {"deviceId": deviceid, "applicationKeySecret": appkeysecret} res = self.session.http.post(self._USER_API, json=json_data) jsonres = self.session.http.json(res, schema=self._USER_SCHEMA) self.usertoken = jsonres["token"] # for authorzation if self.matches["onair"]: onair = self.match["onair"] if onair == "news-global": self._CHANNEL = update_qsd(self._CHANNEL, {"division": "1"}) res = self.session.http.get(self._CHANNEL) jsonres = self.session.http.json(res, schema=self._CHANNEL_SCHEMA) channels = jsonres["channels"] for channel in channels: if onair == channel["id"]: break else: raise NoStreamsError playlisturl = channel["playback"]["hls"] elif self.matches["episode"]: episode = self.match["episode"] if not self._is_playable("episode", episode): log.error("Premium stream is not playable") return {} playlisturl = self._PRGM3U8.format(episode) elif self.matches["slots"]: slots = self.match["slots"] if not self._is_playable("slots", slots): log.error("Premium stream is not playable") return {} playlisturl = self._SLOTM3U8.format(slots) log.debug("URL={0}".format(playlisturl)) # hook abematv private protocol self.session.http.mount("abematv-license://", AbemaTVLicenseAdapter(self.session, deviceid, self.usertoken)) return AbemaTVHLSStream.parse_variant_playlist(self.session, playlisturl) __plugin__ = AbemaTV ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/src/streamlink/plugins/adultswim.py0000644000175100001660000001535115003227510022301 0ustar00runnerdocker""" $description American live TV channel and video on-demand service owned by Warner Bros. $url adultswim.com $type live, vod $region various $notes VODs may be protected by DRM """ import logging import re from urllib.parse import urlparse, urlunparse from streamlink.plugin import Plugin, PluginError, pluginmatcher from streamlink.plugin.api import validate from streamlink.stream.hls import HLSStream from streamlink.utils.parse import parse_json log = logging.getLogger(__name__) @pluginmatcher( re.compile(r"https?://(?:www\.)?adultswim\.com/(streams|videos)(?:/([^/]+))?(?:/([^/]+))?"), ) class AdultSwim(Plugin): token_url = "https://token.ngtv.io/token/token_spe" video_data_url = "https://www.adultswim.com/api/shows/v1/media/{0}/desktop" app_id_js_url_re = re.compile( r"""""", ) truncate_url_re = re.compile(r"""(.*)/\w+/?""") _api_schema = validate.Schema( { "media": { "desktop": { str: { "url": validate.url(), }, }, }, }, validate.get("media"), validate.get("desktop"), validate.filter(lambda k, v: k in ["unprotected", "bulkaes"]), ) _stream_data_schema = validate.Schema( { "props": { "__REDUX_STATE__": { "streams": [ { "id": str, "stream": str, }, ], }, }, }, validate.get("props"), validate.get("__REDUX_STATE__"), validate.get("streams"), ) _token_schema = validate.Schema( validate.any( {"auth": {"token": str}}, {"auth": {"error": {"message": str}}}, ), validate.get("auth"), ) _video_data_schema = validate.Schema( { "props": { "pageProps": { "__APOLLO_STATE__": { str: { validate.optional("id"): str, validate.optional("slug"): str, }, }, }, }, }, validate.get("props"), validate.get("pageProps"), validate.get("__APOLLO_STATE__"), validate.filter(lambda k, v: k.startswith("Video:")), ) def _get_stream_data(self, streamid): res = self.session.http.get(self.url) m = self.json_data_re.search(res.text) if m and m.group(1): streams = parse_json(m.group(1), schema=self._stream_data_schema) else: raise PluginError("Failed to get json_data") for stream in streams: if "id" in stream and streamid == stream["id"] and "stream" in stream: return stream["stream"] def _get_video_data(self, slug): m = self.truncate_url_re.search(self.url) if m and m.group(1): log.debug("Truncated URL={0}".format(m.group(1))) else: raise PluginError("Failed to truncate URL") res = self.session.http.get(m.group(1)) m = self.json_data_re.search(res.text) if m and m.group(1): videos = parse_json(m.group(1), schema=self._video_data_schema) else: raise PluginError("Failed to get json_data") for video in videos: if "slug" in videos[video]: if slug == videos[video]["slug"] and "id" in videos[video]: return videos[video]["id"] def _get_token(self, path): res = self.session.http.get(self.url) m = self.app_id_js_url_re.search(res.text) app_id_js_url = m and m.group(1) if not app_id_js_url: raise PluginError("Could not determine app_id_js_url") log.debug("app_id_js_url={0}".format(app_id_js_url)) res = self.session.http.get(app_id_js_url) m = self.app_id_re.search(res.text) app_id = m and m.group(1) if not app_id: raise PluginError("Could not determine app_id") log.debug("app_id={0}".format(app_id)) res = self.session.http.get( self.token_url, params=dict( format="json", appId=app_id, path=path, ), ) token_data = self.session.http.json(res, schema=self._token_schema) if "error" in token_data: raise PluginError(token_data["error"]["message"]) return token_data["token"] def _get_streams(self): url_type, show_name, episode_name = self.match.groups() if url_type == "streams" and not show_name: url_type = "live-stream" elif not show_name: raise PluginError(f"Missing show_name for url_type: {url_type}") log.debug("URL type={0}".format(url_type)) if url_type == "live-stream": video_id = self._get_stream_data(url_type) elif url_type == "streams": video_id = self._get_stream_data(show_name) elif url_type == "videos": if show_name is None or episode_name is None: raise PluginError( "Missing show_name or episode_name for url_type: {0}".format( url_type, ), ) video_id = self._get_video_data(episode_name) else: raise PluginError("Unrecognised url_type: {0}".format(url_type)) if video_id is None: raise PluginError("Could not find video_id") log.debug("Video ID={0}".format(video_id)) res = self.session.http.get(self.video_data_url.format(video_id)) url_data = self.session.http.json(res, schema=self._api_schema) if "unprotected" in url_data: url = url_data["unprotected"]["url"] elif "bulkaes" in url_data: url_parsed = urlparse(url_data["bulkaes"]["url"]) token = self._get_token(url_parsed.path) url = urlunparse(( url_parsed.scheme, url_parsed.netloc, url_parsed.path, url_parsed.params, "{0}={1}".format("hdnts", token), url_parsed.fragment, )) else: raise PluginError("Could not find a usable URL in url_data") log.debug("URL={0}".format(url)) return HLSStream.parse_variant_playlist(self.session, url) __plugin__ = AdultSwim ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/src/streamlink/plugins/albavision.py0000644000175100001660000001473415003227510022423 0ustar00runnerdocker""" $description TV network with multiple live TV channels broadcasting across various Latin American countries. $url antena7.com.do $url atv.pe $url c9n.com.py $url canal10.com.ni $url canal12.com.sv $url chapintv.com $url elnueve.com.ar $url redbolivision.tv.bo $url repretel.com $url rts.com.ec $url snt.com.py $url tvc.com.ec $url vtv.com.hn $type live $region various """ import logging import re import time from streamlink.plugin import Plugin, pluginmatcher from streamlink.plugin.api import validate from streamlink.stream.hls import HLSStream from streamlink.utils.url import update_qsd log = logging.getLogger(__name__) @pluginmatcher( name="antena7", pattern=re.compile(r"https?://(?:www\.)?antena7\.com\.do/en-?vivo-canal-?\d{1,2}[/#]?$"), ) @pluginmatcher( name="atv", pattern=re.compile(r"https?://(?:www\.)?atv\.pe/envivo-atv(?:mas)?/?"), ) @pluginmatcher( name="c9n", pattern=re.compile(r"https?://(?:www\.)?c9n\.com\.py/envivo/?"), ) @pluginmatcher( name="canal10", pattern=re.compile(r"https?://(?:www\.)?canal10\.com\.ni/envivo/?"), ) @pluginmatcher( name="canal12", pattern=re.compile(r"https?://(?:www\.)?canal12\.com\.sv/envivo/?"), ) @pluginmatcher( name="chapintv", pattern=re.compile(r"https?://(?:www\.)?chapintv\.com/envivo-canal-\d{1,2}/?"), ) @pluginmatcher( name="elnueve", pattern=re.compile(r"https?://(?:www\.)?elnueve\.com\.ar/en-vivo/?"), ) @pluginmatcher( name="redbolivision", pattern=re.compile(r"https?://(?:www\.)?redbolivision\.tv\.bo/(?:envivo-canal-?\d{1,2}|upptv)/?"), ) @pluginmatcher( name="repretel", pattern=re.compile(r"https?://(?:www\.)?repretel\.com/en-?vivo(?:-canal-?\d{1,2})?/?"), ) @pluginmatcher( name="rts", pattern=re.compile(r"https?://(?:www\.)?rts\.com\.ec/envivo/?"), ) @pluginmatcher( name="snt", pattern=re.compile(r"https?://(?:www\.)?snt\.com\.py/envivo/?"), ) @pluginmatcher( name="tvc", pattern=re.compile(r"https?://(?:www\.)?tvc\.com\.ec/envivo/?"), ) @pluginmatcher( name="vtv", pattern=re.compile(r"https?://(?:www\.)?vtv\.com\.hn/envivo/?"), ) class Albavision(Plugin): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._page = None @property def page(self): if self._page is None: self._page = self.session.http.get( self.url, schema=validate.Schema( validate.parse_html(), ), ) return self._page def _is_token_based_site(self): schema = validate.Schema( validate.xml_xpath_string(".//script[contains(text(), 'jQuery.get')]/text()"), ) is_token_based_site = schema.validate(self.page) is not None log.debug(f"is_token_based_site={is_token_based_site}") return is_token_based_site def _get_live_url(self): schema = validate.Schema( validate.xml_xpath_string(".//script[contains(text(), 'LIVE_URL')]/text()"), validate.none_or_all( re.compile(r"""LIVE_URL\s*=\s*(?P['"])(?P.+?)(?P=q)"""), validate.none_or_all( validate.get("url"), validate.url(), ), ), ) live_url = schema.validate(self.page) log.debug(f"live_url={live_url}") return live_url def _get_token_req_url(self): schema = validate.Schema( validate.xml_xpath_string(".//script[contains(text(), 'LIVE_URL')]/text()"), validate.none_or_all( re.compile(r"""jQuery\.get\s*\((?P['"])(?P.+?)(?P=q)"""), validate.none_or_all( validate.get("token"), validate.url(), ), ), ) token_req_host = schema.validate(self.page) log.debug(f"token_req_host={token_req_host}") schema = validate.Schema( validate.xml_xpath_string(".//script[contains(text(), 'LIVE_URL')]/text()"), validate.none_or_all( re.compile(r"""Math\.floor\(Date\.now\(\)\s*/\s*3600000\),\s*(?P['"])(?P.+?)(?P=q)"""), validate.none_or_all(validate.get("token")), ), ) token_req_str = schema.validate(self.page) log.debug(f"token_req_str={token_req_str}") if not token_req_str: return date = int(time.time() // 3600) token_req_token = self.transform_token(token_req_str, date) or self.transform_token(token_req_str, date - 1) if token_req_host and token_req_token: return update_qsd(token_req_host, {"rsk": token_req_token}) def _get_token(self): if not self._is_token_based_site(): return token_req_url = self._get_token_req_url() if not token_req_url: return res = self.session.http.get( token_req_url, schema=validate.Schema( validate.parse_json(), { "success": bool, validate.optional("error"): int, validate.optional("token"): str, }, ), ) if not res["success"]: if res["error"]: log.error(f"Token request failed with error: {res['error']}") else: log.error("Token request failed") return if not res["token"]: log.error("Token not found in response") return token = res["token"] log.debug(f"token={token}") return token @staticmethod def transform_token(token_in, date): token_out = list(token_in) offset = len(token_in) for i in range(offset - 1, -1, -1): p = (i * date) % offset # swap chars at p and i token_out[i], token_out[p] = token_out[p], token_out[i] token_out = "".join(token_out) if token_out.endswith("OK"): return token_out[:-2] else: log.error(f"Invalid site token: {token_in} => {token_out}") def _get_streams(self): live_url = self._get_live_url() if not live_url: log.info("This stream may be off-air or not available in your country") return token = self._get_token() if token: live_url = update_qsd(live_url, {"iut": token}) return HLSStream.parse_variant_playlist(self.session, live_url) __plugin__ = Albavision ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/src/streamlink/plugins/aloula.py0000644000175100001660000001005115003227510021535 0ustar00runnerdocker""" $description Live TV channels and video on-demand service from the SBA, a Saudi, state-owned broadcaster. $url aloula.sa $type live, vod $metadata id $metadata author $metadata category $metadata title """ import logging import re from streamlink.plugin import Plugin, pluginmatcher from streamlink.plugin.api import validate from streamlink.stream.hls import HLSStream log = logging.getLogger(__name__) @pluginmatcher( name="live", pattern=re.compile(r"https?://(?:www\.)?aloula\.sa/(?:\w{2}/)?live/(?P[^/?&]+)"), ) @pluginmatcher( name="vod", pattern=re.compile(r"https?://(?:www\.)?aloula\.sa/(?:\w{2}/)?episode/(?P\d+)"), ) class Aloula(Plugin): def get_live(self, live_slug): live_data = self.session.http.get( "https://aloula.faulio.com/api/v1/channels", schema=validate.Schema( validate.parse_json(), [ { "id": int, "url": str, "title": str, "has_live": bool, "has_vod": bool, "streams": { "hls": validate.url(), }, }, ], validate.filter(lambda k: k["url"] == live_slug), ), ) if not live_data: return live_data = live_data[0] log.trace(f"{live_data!r}") if not live_data["has_live"]: log.error("Stream is not live") return self.id = live_data["id"] self.author = "SBA" self.title = live_data["title"] self.category = "Live" return HLSStream.parse_variant_playlist(self.session, live_data["streams"]["hls"]) def get_vod(self, vod_id): vod_data = self.session.http.get( f"https://aloula.faulio.com/api/v1/video/{vod_id}", acceptable_status=(200, 401), schema=validate.Schema( validate.parse_json(), validate.any( validate.all( { "blocks": [ { "id": str, "program_title": str, "title": str, "season_number": int, "episode": int, }, ], }, validate.get(("blocks", 0)), ), {"cms_error": str, "message": str}, ), ), ) log.trace(f"{vod_data!r}") if "cms_error" in vod_data and vod_data["cms_error"] == "auth": log.error("This stream requires a login; specify appropriate Authorization and profile HTTP headers") return if "cms_error" in vod_data: log.error(f"API error: {vod_data['cms_error']} ({vod_data['message']})") return self.id = vod_data["id"] self.author = vod_data["program_title"] self.title = vod_data["title"] self.category = f"S{vod_data['season_number']}E{vod_data['episode']}" hls_url = self.session.http.get( f"https://aloula.faulio.com/api/v1/video/{vod_id}/player", schema=validate.Schema( validate.parse_json(), {"settings": {"protocols": {"hls": validate.url()}}}, validate.get(("settings", "protocols", "hls")), ), ) return HLSStream.parse_variant_playlist(self.session, hls_url) def _get_streams(self): self.session.http.headers.update({ "Origin": "https://www.aloula.sa", "Referer": "https://www.aloula.sa/", }) if self.matches["live"]: return self.get_live(self.match["live_slug"]) else: return self.get_vod(self.match["vod_id"]) __plugin__ = Aloula ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/src/streamlink/plugins/app17.py0000644000175100001660000000421415003227510021214 0ustar00runnerdocker""" $description Social platform delivering live broadcasts on diverse topics, from politics and music to entertainment. $url 17app.co $type live """ import logging import re from streamlink.plugin import Plugin, pluginmatcher from streamlink.plugin.api import validate from streamlink.stream.hls import HLSStream from streamlink.stream.http import HTTPStream log = logging.getLogger(__name__) @pluginmatcher( re.compile(r"https?://17\.live/.+/live/(?P[^/&?]+)"), ) class App17(Plugin): def _get_streams(self): channel = self.match.group("channel") self.session.http.headers.update({"Referer": self.url}) data = self.session.http.post( f"https://wap-api.17app.co/api/v1/lives/{channel}/viewers/alive", data={"liveStreamID": channel}, schema=validate.Schema( validate.parse_json(), validate.any( { "rtmpUrls": [ { validate.optional("provider"): validate.any(int, None), "url": validate.url(path=validate.endswith(".flv")), }, ], }, {"errorCode": int, "errorMessage": str}, ), ), acceptable_status=(200, 403, 404, 420), ) log.trace(f"{data!r}") if data.get("errorCode"): log.error(f"{data['errorCode']} - {data['errorMessage'].replace('Something wrong: ', '')}") return flv_url = data["rtmpUrls"][0]["url"] yield "live", HTTPStream(self.session, flv_url) if "wansu-" in flv_url: hls_url = flv_url.replace(".flv", "/playlist.m3u8") else: hls_url = flv_url.replace("live-hdl", "live-hls").replace(".flv", ".m3u8") s = HLSStream.parse_variant_playlist(self.session, hls_url) if not s: yield "live", HLSStream(self.session, hls_url) elif len(s) == 1: yield "live", next(iter(s.values())) else: yield from s.items() __plugin__ = App17 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/src/streamlink/plugins/ard_live.py0000644000175100001660000000577215003227510022063 0ustar00runnerdocker""" $description Live TV channels and video on-demand service from ARD, a German public, independent broadcaster. $url daserste.de $type live, vod $metadata title $region Germany """ import logging import re from urllib.parse import urljoin from streamlink.plugin import Plugin, PluginError, pluginmatcher from streamlink.plugin.api import validate from streamlink.stream.hls import HLSStream from streamlink.stream.http import HTTPStream log = logging.getLogger(__name__) @pluginmatcher( re.compile(r"https?://((www|live)\.)?daserste\.de/"), ) class ARDLive(Plugin): _URL_DATA_BASE = "https://www.daserste.de/" _QUALITY_MAP = { 4: "1080p", 3: "720p", 2: "540p", 1: "270p", 0: "180p", } def _get_streams(self): try: data_url = self.session.http.get( self.url, schema=validate.Schema( validate.parse_html(), validate.xml_find(".//*[@data-ctrl-player]"), validate.get("data-ctrl-player"), validate.transform(lambda s: s.replace("'", '"')), validate.parse_json(), {"url": str}, validate.get("url"), ), ) except PluginError: return data_url = urljoin(self._URL_DATA_BASE, data_url) log.debug(f"Player URL: '{data_url}'") self.title, media = self.session.http.get( data_url, schema=validate.Schema( validate.parse_json(name="MEDIAINFO"), { "mc": { validate.optional("_title"): str, "_mediaArray": [ validate.all( { "_mediaStreamArray": [ validate.all( { "_quality": validate.any(str, int), "_stream": [validate.url()], }, validate.union_get("_quality", ("_stream", 0)), ), ], }, validate.get("_mediaStreamArray"), validate.transform(dict), ), ], }, }, validate.get("mc"), validate.union_get("_title", ("_mediaArray", 0)), ), ) if media.get("auto"): yield from HLSStream.parse_variant_playlist(self.session, media.get("auto")).items() else: for quality, stream in media.items(): yield self._QUALITY_MAP.get(quality, quality), HTTPStream(self.session, stream) __plugin__ = ARDLive ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/src/streamlink/plugins/ard_mediathek.py0000644000175100001660000001252115003227510023045 0ustar00runnerdocker""" $description Live TV channels and video on-demand service from ARD, a German public, independent broadcaster. $url ardmediathek.de $url mediathek.daserste.de $type live, vod $metadata title $region Germany """ import logging import re from streamlink.plugin import Plugin, pluginmatcher from streamlink.plugin.api import validate from streamlink.stream.hls import HLSStream from streamlink.stream.http import HTTPStream log = logging.getLogger(__name__) @pluginmatcher( name="live", pattern=re.compile(r"https?://(\w+\.)?ardmediathek\.de/live/(?:[^/]+/)?(?P\w+)(?:\?|$)"), ) @pluginmatcher( name="video", pattern=re.compile(r"https?://(\w+\.)?ardmediathek\.de/video/(?:[^/]+/[^/]+/[^/]+/)?(?P\w+)(?:\?|$)"), ) class ARDMediathek(Plugin): _URL_API = "https://api.ardmediathek.de/page-gateway/pages/ard/item/{item}" _QUALITY_MAP = { 4: "1080p", 3: "720p", 2: "540p", 1: "360p", 0: "270p", } def _get_streams(self): data_json = self.session.http.get( self.url, schema=validate.Schema( validate.parse_html(), validate.xml_xpath_string(".//script[@type='application/json'][@id='fetchedContextValue2'][1]/text()"), validate.none_or_all( validate.parse_json(), [validate.list(str, {"data": dict})], validate.filter(lambda item: item[0].startswith("https://api.ardmediathek.de/page-gateway/pages/")), validate.any( validate.get((0, 1, "data")), [], ), ), ), ) if not data_json: data_json = self.session.http.get( self._URL_API.format(item=self.match["id_live"] if self.matches["live"] else self.match["id_video"]), params={ "devicetype": "pc", "embedded": "false", }, schema=validate.Schema(validate.parse_json()), ) if not data_json: return schema_data = validate.Schema({ "id": str, "widgets": validate.all( [dict], validate.filter(lambda item: item.get("mediaCollection")), validate.get(0), validate.any( None, validate.all( { "geoblocked": bool, "publicationService": { "name": str, }, "show": validate.any( None, validate.all( {"title": str}, validate.get("title"), ), ), "title": str, "mediaCollection": { "embedded": { "_mediaArray": [ validate.all( { "_mediaStreamArray": [ validate.all( { "_quality": validate.any(str, int), "_stream": validate.url(), }, validate.union_get("_quality", "_stream"), ), ], }, validate.get("_mediaStreamArray"), validate.transform(dict), ), ], }, }, }, validate.union_get( "geoblocked", ("mediaCollection", "embedded", "_mediaArray", 0), ("publicationService", "name"), "title", "show", ), ), ), ), }) data = schema_data.validate(data_json) log.debug(f"Found media id: {data['id']}") if not data["widgets"]: log.info("The content is unavailable") return geoblocked, media, self.author, self.title, show = data["widgets"] if geoblocked: log.info("The content is not available in your region") return if show: self.title = f"{show}: {self.title}" if media.get("auto"): yield from HLSStream.parse_variant_playlist(self.session, media.get("auto")).items() else: for quality, stream in media.items(): yield self._QUALITY_MAP.get(quality, quality), HTTPStream(self.session, stream) __plugin__ = ARDMediathek ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/src/streamlink/plugins/artetv.py0000644000175100001660000000466015003227510021576 0ustar00runnerdocker""" $description European public service channel promoting culture, including magazine shows, concerts and documentaries. $url arte.tv $type live, vod $metadata id $metadata title """ import logging import re from operator import itemgetter from streamlink.plugin import Plugin, pluginmatcher from streamlink.plugin.api import validate from streamlink.stream.hls import HLSStream log = logging.getLogger(__name__) @pluginmatcher( name="live", pattern=re.compile( r"https?://(?:\w+\.)?arte\.tv/(?P[a-z]{2})/(?:direct|live)/?", ), ) @pluginmatcher( name="vod", pattern=re.compile( r"https?://(?:\w+\.)?arte\.tv/(?:guide/)?(?P[a-z]{2})/(?:videos/)?(?P(?!RC-|videos)[^/]+?)/.+", ), ) class ArteTV(Plugin): API_URL = "https://api.arte.tv/api/player/v2/config/{language}/{id}" def _get_streams(self): self.id = self.match["video_id"] if self.matches["vod"] else "LIVE" json_url = self.API_URL.format( language=self.match["language"], id=self.id, ) streams, metadata = self.session.http.get( json_url, schema=validate.Schema( validate.parse_json(), {"data": {"attributes": dict}}, validate.get(("data", "attributes")), { "streams": validate.any( [], [ validate.all( { "slot": int, "protocol": str, "url": validate.url(), }, validate.union_get("slot", "protocol", "url"), ), ], ), "metadata": { "title": str, "subtitle": validate.any(None, str), }, }, validate.union_get("streams", "metadata"), ), ) self.title = f"{metadata['title']} - {metadata['subtitle']}" if metadata["subtitle"] else metadata["title"] for _slot, protocol, url in sorted(streams, key=itemgetter(0)): if "HLS" not in protocol: continue return HLSStream.parse_variant_playlist(self.session, url) __plugin__ = ArteTV ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/src/streamlink/plugins/atpchallenger.py0000644000175100001660000000155515003227510023102 0ustar00runnerdocker""" $description Tennis tournaments organized by the Association of Tennis Professionals. $url atptour.com/en/atp-challenger-tour/challenger-tv $type live, vod """ import re from streamlink.plugin import Plugin, pluginmatcher from streamlink.plugin.api import validate @pluginmatcher( re.compile(r"https?://(?:www\.)?atptour\.com/(?:en|es)/atp-challenger-tour/challenger-tv"), ) class AtpChallengerTour(Plugin): def _get_streams(self): iframe_url = self.session.http.get( self.url, schema=validate.Schema( validate.parse_html(), validate.xml_xpath_string(".//iframe[starts-with(@id,'vimeoPlayer_')][@src][1]/@src"), validate.any(None, validate.url()), ), ) if iframe_url: return self.session.streams(iframe_url) __plugin__ = AtpChallengerTour ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/src/streamlink/plugins/atresplayer.py0000644000175100001660000000624715003227510022627 0ustar00runnerdocker""" $description Spanish live TV channels from Atresmedia Television, including Antena 3 and laSexta. $url atresplayer.com $type live $region Spain """ import logging import re from streamlink.plugin import Plugin, pluginmatcher from streamlink.plugin.api import validate from streamlink.stream.dash import DASHStream from streamlink.stream.hls import HLSStream from streamlink.utils.url import update_scheme log = logging.getLogger(__name__) @pluginmatcher( re.compile(r"https?://(?:www\.)?atresplayer\.com/directos/.+"), ) class AtresPlayer(Plugin): _channels_api_url = "https://api.atresplayer.com/client/v1/info/channels" _player_api_url = "https://api.atresplayer.com/player/v1/live/{channel_id}?NODRM=true" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.url = update_scheme("https://", f"{self.url.rstrip('/')}/") def _get_streams(self): channel_path = f"/{self.url.split('/')[-2]}/" channel_data = self.session.http.get( self._channels_api_url, schema=validate.Schema( validate.parse_json(), [ { "id": str, "link": {"url": str}, }, ], validate.filter(lambda item: item["link"]["url"] == channel_path), ), ) if not channel_data: return channel_id = channel_data[0]["id"] player_api_url = self._player_api_url.format(channel_id=channel_id) log.debug(f"Player API URL: {player_api_url}") sources = self.session.http.get( player_api_url, acceptable_status=(200, 403), schema=validate.Schema( validate.parse_json(), validate.any( { "error": str, "error_description": str, }, { "sourcesLive": [ validate.all( { "src": validate.url(), validate.optional("type"): str, }, validate.union_get("type", "src"), ), ], }, ), ), ) if "error" in sources: log.error(f"Player API error: {sources['error']} - {sources['error_description']}") return for streamtype, streamsrc in sources.get("sourcesLive"): log.debug(f"Stream source: {streamsrc} ({streamtype or 'n/a'})") if streamtype == "application/vnd.apple.mpegurl": streams = HLSStream.parse_variant_playlist(self.session, streamsrc) if not streams: yield "live", HLSStream(self.session, streamsrc) else: yield from streams.items() elif streamtype == "application/dash+xml": yield from DASHStream.parse_manifest(self.session, streamsrc).items() __plugin__ = AtresPlayer ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/src/streamlink/plugins/bbciplayer.py0000644000175100001660000002060015003227510022375 0ustar00runnerdocker""" $description Live TV channels and video on-demand service from the BBC, a British public, state-owned broadcaster. $url bbc.co.uk/iplayer $type live, vod $region United Kingdom """ import base64 import logging import re from collections import defaultdict from hashlib import sha1 from urllib.parse import urlparse, urlunparse from streamlink.plugin import Plugin, PluginError, pluginargument, pluginmatcher from streamlink.plugin.api import validate from streamlink.stream.dash import DASHStream from streamlink.stream.hls import HLSStream from streamlink.utils.parse import parse_json log = logging.getLogger(__name__) @pluginmatcher( name="live", pattern=re.compile(r"https?://(?:www\.)?bbc\.co\.uk/iplayer/live/(?P\w+)"), ) @pluginmatcher( name="episode", pattern=re.compile(r"https?://(?:www\.)?bbc\.co\.uk/iplayer/episode/(?P\w+)"), ) @pluginargument( "username", requires=["password"], metavar="USERNAME", help="The username used to register with bbc.co.uk.", ) @pluginargument( "password", prompt="Enter bbc.co.uk account password", sensitive=True, metavar="PASSWORD", help="A bbc.co.uk account password to use with --bbciplayer-username.", ) @pluginargument( "hd", action="store_true", help="Prefer HD streams over local SD streams, some live programmes may not be broadcast in HD.", ) class BBCiPlayer(Plugin): """ Allows streaming of live channels from bbc.co.uk/iplayer/live/* and of iPlayer programmes from bbc.co.uk/iplayer/episode/* """ mediator_re = re.compile(r"window\.__IPLAYER_REDUX_STATE__\s*=\s*({.*?});", re.DOTALL) state_re = re.compile(r"window.__IPLAYER_REDUX_STATE__\s*=\s*({.*?});") account_locals_re = re.compile(r"window.bbcAccount.locals\s*=\s*({.*?});") hash = base64.b64decode(b"N2RmZjc2NzFkMGM2OTdmZWRiMWQ5MDVkOWExMjE3MTk5MzhiOTJiZg==") api_url = ( "https://open.live.bbc.co.uk/mediaselector/6/select/version/2.0/mediaset/" + "{platform}/vpid/{vpid}/format/json/atk/{vpid_hash}/asn/1/" ) platforms = ("pc", "iptv-all") session_url = "https://session.bbc.com/session" auth_url = "https://account.bbc.com/signin" mediator_schema = validate.Schema( { "versions": [{"id": str}], }, validate.get("versions"), validate.get(0), validate.get("id"), ) mediaselector_schema = validate.Schema( validate.parse_json(), { "media": [ { "connection": validate.all( [ { validate.optional("href"): validate.url(), validate.optional("transferFormat"): str, }, ], validate.filter(lambda c: c.get("href")), ), "kind": str, }, ], }, validate.get("media"), validate.filter(lambda x: x["kind"] == "video"), ) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.url = urlunparse(urlparse(self.url)._replace(scheme="https")) @classmethod def _hash_vpid(cls, vpid): return sha1(cls.hash + str(vpid).encode("utf8")).hexdigest() def find_vpid(self, url, res=None): """ Find the Video Packet ID in the HTML for the provided URL :param url: URL to download, if res is not provided. :param res: Provide a cached version of the HTTP response to search :type url: string :type res: requests.Response :return: Video Packet ID for a Programme in iPlayer :rtype: string """ log.debug(f"Looking for vpid on {url}") # Use pre-fetched page if available res = res or self.session.http.get(url) m = self.mediator_re.search(res.text) return m and parse_json(m.group(1), schema=self.mediator_schema) def find_tvip(self, url, master=False): log.debug("Looking for {0} tvip on {1}".format("master" if master else "", url)) res = self.session.http.get(url) m = self.state_re.search(res.text) data = m and parse_json(m.group(1)) if data: channel = data.get("channel") if master: return channel.get("masterBrand") return channel.get("id") def mediaselector(self, vpid): urls = defaultdict(set) for platform in self.platforms: url = self.api_url.format(vpid=vpid, vpid_hash=self._hash_vpid(vpid), platform=platform) log.debug(f"Info API request: {url}") medias = self.session.http.get(url, schema=self.mediaselector_schema) for media in medias: for connection in media["connection"]: urls[connection.get("transferFormat")].add(connection["href"]) for stream_type, urlitems in urls.items(): log.debug(f"{len(urlitems)} {stream_type} streams") for url in list(urlitems): try: if stream_type == "hls": yield from HLSStream.parse_variant_playlist(self.session, url).items() if stream_type == "dash": yield from DASHStream.parse_manifest(self.session, url).items() log.debug(f" OK: {url}") except Exception: log.debug(f" FAIL: {url}") def login(self, ptrt_url): """ Create session using BBC ID. See https://www.bbc.co.uk/usingthebbc/account/ :param ptrt_url: The snapback URL to redirect to after successful authentication :type ptrt_url: string :return: Whether authentication was successful :rtype: bool """ def auth_check(res): return ptrt_url in ([h.url for h in res.history] + [res.url]) # make the session request to get the correct cookies session_res = self.session.http.get( self.session_url, params=dict(ptrt=ptrt_url), ) if auth_check(session_res): log.debug("Already authenticated, skipping authentication") return True res = self.session.http.post( self.auth_url, params=urlparse(session_res.url).query, data=dict( jsEnabled=True, username=self.get_option("username"), password=self.get_option("password"), attempts=0, ), headers={"Referer": self.url}, ) return auth_check(res) def _get_streams(self): if not self.get_option("username"): log.error( "BBC iPlayer requires an account. You must login using --bbciplayer-username and --bbciplayer-password", ) return log.info( "A TV License is required to watch BBC iPlayer streams, see the BBC website for more " + "information: https://www.bbc.co.uk/iplayer/help/tvlicence", ) if not self.login(self.url): log.error("Could not authenticate, check your username and password") return if self.matches["episode"]: episode_id = self.match["episode_id"] log.debug(f"Loading streams for episode: {episode_id}") vpid = self.find_vpid(self.url) if vpid: log.debug(f"Found VPID: {vpid}") yield from self.mediaselector(vpid) else: log.error(f"Could not find VPID for episode {episode_id}") elif self.matches["live"]: channel_name = self.match["channel_name"] log.debug(f"Loading stream for live channel: {channel_name}") if self.get_option("hd"): tvip = f"{self.find_tvip(self.url, master=True)}_hd" if tvip: log.debug(f"Trying HD stream {tvip}...") try: yield from self.mediaselector(tvip) except PluginError: log.error("Failed to get HD streams, falling back to SD") else: return tvip = self.find_tvip(self.url) if tvip: log.debug(f"Found TVIP: {tvip}") yield from self.mediaselector(tvip) __plugin__ = BBCiPlayer ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/src/streamlink/plugins/bfmtv.py0000644000175100001660000001002515003227510021377 0ustar00runnerdocker""" $description French live TV channels, live streams and video content, owned by NextRadioTV. $url bfmtv.com $url 01net.com $type live, vod """ import logging import re from urllib.parse import urljoin from streamlink.plugin import Plugin, PluginError, pluginmatcher from streamlink.plugin.api import validate from streamlink.plugins.brightcove import BrightcovePlayer from streamlink.stream.http import HTTPStream log = logging.getLogger(__name__) @pluginmatcher( re.compile(r"https?://(?:[\w-]+\.)+(?:bfmtv|01net)\.com"), ) class BFMTV(Plugin): def _brightcove(self, account_id, video_id): log.debug(f"Account ID: {account_id}") log.debug(f"Video ID: {video_id}") player = BrightcovePlayer(self.session, account_id) return dict(player.get_streams(video_id)) def _streams_brightcove(self, root): schema_brightcove = validate.Schema( validate.any( validate.all( validate.xml_find(".//*[@accountid][@videoid]"), validate.union_get("accountid", "videoid"), ), validate.all( validate.xml_find(".//*[@data-account][@data-video-id]"), validate.union_get("data-account", "data-video-id"), ), ), ) try: account_id, video_id = schema_brightcove.validate(root) except PluginError: return return self._brightcove(account_id, video_id) def _streams_brightcove_js(self, root): re_js_src = re.compile(r"^[\w/]+/main\.\w+\.js$") schema_brightcove_js = validate.Schema( validate.xml_findall(r".//script[@src]"), validate.filter(lambda elem: re_js_src.search(elem.attrib.get("src")) is not None), validate.get(0), str, validate.transform(lambda src: urljoin(self.url, src)), ) schema_brightcove_js2 = validate.Schema( re.compile(r"""i\?\([A-Z]="[^"]+",y="(?P\d+).*"data-account"\s*:\s*"(?P\d+)"""), validate.union_get("account_id", "video_id"), ) try: js_url = schema_brightcove_js.validate(root) log.debug(f"JS URL: {js_url}") account_id, video_id = self.session.http.get(js_url, schema=schema_brightcove_js2) except PluginError: return return self._brightcove(account_id, video_id) def _streams_dailymotion(self, root): schema_dailymotion = validate.Schema( validate.xml_xpath_string(".//iframe[contains(@src,'dailymotion.com/')][1]/@src"), str, validate.transform(lambda src: src.split("/")[-1]), ) try: video_id = schema_dailymotion.validate(root) except PluginError: return log.debug(f"Found dailymotion video ID: {video_id}") return self.session.streams(f"https://www.dailymotion.com/embed/video/{video_id}") def _streams_audio(self, root): schema_audio = validate.Schema( validate.any( validate.all( validate.xml_xpath_string(".//audio/source[contains(@src,'.mp3')][1]/@src"), str, ), validate.all( validate.xml_xpath_string(".//div[contains(@class,'audio-player')][@data-media-url][1]/@data-media-url"), str, ), ), ) try: audio_url = schema_audio.validate(root) except PluginError: return return {"audio": HTTPStream(self.session, audio_url)} def _get_streams(self): root = self.session.http.get( self.url, schema=validate.Schema( validate.parse_html(), ), ) return ( self._streams_brightcove(root) or self._streams_dailymotion(root) or self._streams_brightcove_js(root) or self._streams_audio(root) ) __plugin__ = BFMTV ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/src/streamlink/plugins/bigo.py0000644000175100001660000000352015003227510021203 0ustar00runnerdocker""" $description Global live-streaming platform for live video game broadcasts and individual live streams. $url bigo.tv $type live $metadata id $metadata author $metadata category $metadata title """ import logging import re from streamlink.plugin import Plugin, pluginmatcher from streamlink.plugin.api import validate from streamlink.stream.hls import HLSStream log = logging.getLogger(__name__) @pluginmatcher( re.compile(r"https?://(?:www\.)?bigo\.tv/(?P[^/]+)$"), ) class Bigo(Plugin): _URL_API = "https://ta.bigo.tv/official_website/studio/getInternalStudioInfo" def _get_streams(self): self.id, self.author, self.category, self.title, hls_url = self.session.http.post( self._URL_API, params={ "siteId": self.match["site_id"], "verify": "", }, schema=validate.Schema( validate.parse_json(), { "code": 0, "msg": "success", "data": { "roomId": validate.any(None, str), "clientBigoId": validate.any(None, str), "gameTitle": str, "roomTopic": str, "hls_src": validate.any(None, "", validate.url()), }, }, validate.union_get( ("data", "roomId"), ("data", "clientBigoId"), ("data", "gameTitle"), ("data", "roomTopic"), ("data", "hls_src"), ), ), ) if not self.id: return if not hls_url: log.info("Channel is offline") return yield "live", HLSStream(self.session, hls_url) __plugin__ = Bigo ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/src/streamlink/plugins/bilibili.py0000644000175100001660000002033515003227510022045 0ustar00runnerdocker""" $description Chinese video sharing website based in Shanghai, themed around animation, comics, and games (ACG). $url live.bilibili.com $type live """ import logging import re from streamlink.exceptions import NoStreamsError from streamlink.plugin import Plugin, pluginmatcher from streamlink.plugin.api import validate from streamlink.stream import HTTPStream from streamlink.stream.hls import HLSStream log = logging.getLogger(__name__) @pluginmatcher( re.compile(r"https?://live\.bilibili\.com/(?P[^/?#]+)"), ) class Bilibili(Plugin): _URL_API_V1_PLAYURL = "https://api.live.bilibili.com/room/v1/Room/playUrl" _URL_API_V2_PLAYINFO = "https://api.live.bilibili.com/xlive/web-room/v2/index/getRoomPlayInfo" SHOW_STATUS_OFFLINE = 0 SHOW_STATUS_ONLINE = 1 SHOW_STATUS_ROUND = 2 @classmethod def stream_weight(cls, stream): offset = 1 if "_alt" in stream else 0 if stream.startswith("httpstream"): return 4 - offset, stream if stream.startswith("hls"): return 2 - offset, stream return super().stream_weight(stream) def _get_api_v1_playurl(self, room_id): return self.session.http.get( self._URL_API_V1_PLAYURL, params={ "cid": room_id, "platform": "web", "quality": "4", }, schema=validate.Schema( validate.parse_json(), validate.any( validate.all( { "code": 0, "data": { "durl": [ validate.all( { "url": validate.url(), }, validate.get("url"), ), ], }, }, validate.get(("data", "durl")), ), validate.all( { "code": int, }, validate.transform(lambda _: []), ), ), ), ) @property def _schema_v2_streams(self): return validate.all( [ { "protocol_name": str, "format": validate.all( [ { "format_name": str, "codec": validate.all( [ { "codec_name": str, "base_url": str, "url_info": [ { "host": validate.url(), "extra": str, }, ], }, ], validate.filter(lambda item: item["codec_name"] == "avc"), ), }, ], validate.filter(lambda item: item["format_name"] in ("fmp4", "ts")), ), }, ], validate.filter(lambda item: item["protocol_name"] == "http_hls"), ) def _get_api_v2_playinfo(self, room_id): return self.session.http.get( self._URL_API_V2_PLAYINFO, params={ "room_id": room_id, "no_playurl": 0, "mask": 1, "qn": 0, "platform": "web", "protocol": "0,1", "format": "0,1,2", "codec": "0,1,2", "dolby": 5, "panorama": 1, }, schema=validate.Schema( validate.parse_json(), { "code": 0, "data": { "playurl_info": validate.none_or_all( { "playurl": { "stream": self._schema_v2_streams, }, }, validate.get(("playurl", "stream")), ), }, }, validate.get(("data", "playurl_info")), ), ) def _get_page_playinfo(self): data = self.session.http.get( self.url, schema=validate.Schema( validate.parse_html(), validate.xml_xpath_string(".//script[contains(text(),'window.__NEPTUNE_IS_MY_WAIFU__={')][1]/text()"), validate.none_or_all( validate.transform(str.replace, "window.__NEPTUNE_IS_MY_WAIFU__=", ""), validate.parse_json(), { "roomInitRes": { "data": { "live_status": int, "playurl_info": validate.none_or_all( { "playurl": { "stream": self._schema_v2_streams, }, }, validate.get(("playurl", "stream")), ), }, }, "roomInfoRes": { "data": { "room_info": { "live_id": int, "title": str, "area_name": str, }, "anchor_info": { "base_info": { "uname": str, }, }, }, }, }, validate.union_get( ("roomInfoRes", "data", "room_info", "live_id"), ("roomInfoRes", "data", "anchor_info", "base_info", "uname"), ("roomInfoRes", "data", "room_info", "area_name"), ("roomInfoRes", "data", "room_info", "title"), ("roomInitRes", "data", "live_status"), ("roomInitRes", "data", "playurl_info"), ), ), ), ) if not data: return self.id, self.author, self.category, self.title, live_status, streams = data if live_status != self.SHOW_STATUS_ONLINE: log.info("Channel is offline") raise NoStreamsError return streams def _get_streams(self): http_streams = self._get_api_v1_playurl(self.match["channel"]) for http_stream in http_streams: if self.session.http.head(http_stream, raise_for_status=False).status_code >= 400: continue yield "httpstream", HTTPStream(self.session, http_stream) hls_streams = self._get_page_playinfo() if not hls_streams: log.debug("Falling back to _get_api_v2_playinfo()") hls_streams = self._get_api_v2_playinfo(self.match["channel"]) for hls_stream in hls_streams or []: for stream_format in hls_stream["format"]: for codec in stream_format["codec"]: for url_info in codec["url_info"]: url = f"{url_info['host']}{codec['base_url']}{url_info['extra']}" yield "hls", HLSStream(self.session, url) __plugin__ = Bilibili ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/src/streamlink/plugins/blazetv.py0000644000175100001660000000707215003227510021740 0ustar00runnerdocker""" $description British live TV channel and video on-demand service from Blaze, owned by A&E Networks UK. $url blaze.tv $type live, vod $metadata id $metadata author $metadata category $metadata title $region United Kingdom """ import logging import re from streamlink.plugin import Plugin, pluginmatcher from streamlink.plugin.api import validate from streamlink.stream.hls import HLSStream log = logging.getLogger(__name__) @pluginmatcher( re.compile(r"https?://(?:watch\.)?blaze\.tv/(?:(?Plive)|watch/replay/\d+)"), ) class BlazeTV(Plugin): @staticmethod def _get_live_uvid(parsed_html): schema = validate.Schema( validate.xml_xpath_string(".//div[@id='live-player-root']/@data-player-uvid"), ) return schema.validate(parsed_html) @staticmethod def _get_vod_uvid(parsed_html): schema = validate.Schema( validate.xml_xpath_string(".//script[contains(text(), 'window.nowPlaying.setData')]"), validate.none_or_all( re.compile(r"window\.nowPlaying\.setData\(({.*?})\);"), validate.none_or_all( validate.get(1), validate.parse_json(), { "id": str, "series_title": str, "title": str, "season": str, "episode": str, }, ), ), ) return schema.validate(parsed_html) def _get_tokenizer(self, streamtype, uvid): return self.session.http.get( f"https://watch.blaze.tv/stream/{streamtype}/widevine/{uvid}", schema=validate.Schema( validate.parse_json(), { "tokenizer": { "url": validate.url(), "uvid": str, "expiry": int, "token": str, }, }, validate.get("tokenizer"), ), ) def _get_streams(self): is_live = self.match.group("is_live") parsed_html = self.session.http.get(self.url, schema=validate.Schema(validate.parse_html())) if is_live: uvid = self._get_live_uvid(parsed_html) if not uvid or not uvid.isdecimal(): return token_data = self._get_tokenizer("live", uvid) self.id = uvid self.author = "Blaze" self.title = "Live TV" self.category = "Live" else: data = self._get_vod_uvid(parsed_html) if not data or not data["id"] or not data["id"].isdecimal(): return token_data = self._get_tokenizer("replay", data["id"]) self.id = data["id"] self.author = data["series_title"] self.title = data["title"] self.category = f"S{data['season']}E{data['episode']}" log.trace(f"token_data={token_data!r}") hls_url = self.session.http.get( token_data["url"], headers={ "Token": token_data["token"], "Token-Expiry": str(token_data["expiry"]), "Uvid": token_data["uvid"], }, schema=validate.Schema( validate.parse_json(), {"Streams": {"Adaptive": validate.url()}}, validate.get(("Streams", "Adaptive")), ), ) return HLSStream.parse_variant_playlist(self.session, hls_url) __plugin__ = BlazeTV ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/src/streamlink/plugins/bloomberg.py0000644000175100001660000001241115003227510022232 0ustar00runnerdocker""" $description America-based television network centred towards business and capital market programming. $url bloomberg.com $type live, vod $metadata title """ import logging import re from streamlink.plugin import Plugin, PluginError, pluginmatcher from streamlink.plugin.api import useragents, validate from streamlink.stream.hls import HLSStream log = logging.getLogger(__name__) @pluginmatcher( name="live", pattern=re.compile(r"https?://(?:www\.)?bloomberg\.com/live(?:/(?P[^/]+))?"), ) @pluginmatcher( name="vod", pattern=re.compile(r"https?://(?:www\.)?bloomberg\.com/news/videos/[^/]+/[^/]+"), ) class Bloomberg(Plugin): LIVE_API_URL = "https://cdn.gotraffic.net/projector/latest/assets/config/config.min.json?v=1" VOD_API_URL = "https://www.bloomberg.com/api/embed?id={0}" DEFAULT_CHANNEL = "us" def _get_live_streams(self, data, channel): schema_live_ids = validate.Schema( { "live": { "channels": { "byChannelId": { channel: validate.all( {"liveId": str}, validate.get("liveId"), ), }, }, }, }, validate.get(("live", "channels", "byChannelId", channel)), ) try: live_id = schema_live_ids.validate(data) except PluginError: log.error(f"Could not find liveId for channel '{channel}'") return log.debug(f"Found liveId: {live_id}") return self.session.http.get( self.LIVE_API_URL, schema=validate.Schema( validate.parse_json(), { "livestreams": { live_id: { validate.optional("cdns"): validate.all( [ { "streams": [ { "url": validate.url(), }, ], }, ], validate.transform(lambda x: [urls["url"] for y in x for urls in y["streams"]]), ), }, }, }, validate.get(("livestreams", live_id, "cdns")), ), ) def _get_vod_streams(self, data): schema_vod_list = validate.Schema( validate.any( validate.all( {"video": {"videoStory": dict}}, validate.get(("video", "videoStory")), ), validate.all( {"quicktakeVideo": {"videoStory": dict}}, validate.get(("quicktakeVideo", "videoStory")), ), ), { "video": { "bmmrId": str, }, }, validate.get(("video", "bmmrId")), ) schema_url = validate.all( {"url": validate.url()}, validate.get("url"), ) try: video_id = schema_vod_list.validate(data) except PluginError: log.error("Could not find videoId") return log.debug(f"Found videoId: {video_id}") vod_url = self.VOD_API_URL.format(video_id) secureStreams, streams, self.title = self.session.http.get( vod_url, schema=validate.Schema( validate.parse_json(), { validate.optional("secureStreams"): [schema_url], validate.optional("streams"): [schema_url], "title": str, }, validate.union_get("secureStreams", "streams", "title"), ), ) return secureStreams or streams def _get_streams(self): self.session.http.headers.clear() self.session.http.headers["User-Agent"] = useragents.CHROME data = self.session.http.get( self.url, schema=validate.Schema( validate.parse_html(), validate.xml_xpath_string(".//script[contains(text(),'window.__PRELOADED_STATE__')][1]/text()"), validate.none_or_all( re.compile(r"\bwindow\.__PRELOADED_STATE__\s*=\s*(?P{.+?})\s*;(?:\s|$)"), validate.none_or_all( validate.get("json"), validate.parse_json(), ), ), ), ) if not data: log.error("Could not find JSON data. Invalid URL or bot protection...") return if self.matches["live"]: streams = self._get_live_streams(data, self.match["channel"] or self.DEFAULT_CHANNEL) else: streams = self._get_vod_streams(data) if streams: # just return the first stream return HLSStream.parse_variant_playlist(self.session, streams[0]) __plugin__ = Bloomberg ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/src/streamlink/plugins/brightcove.py0000644000175100001660000000734015003227510022423 0ustar00runnerdocker""" $description Global live-streaming and video on-demand hosting platform. $url players.brightcove.net $type live, vod $metadata title """ import logging import re from urllib.parse import urlparse from streamlink.plugin import Plugin, PluginError, pluginmatcher from streamlink.plugin.api import validate from streamlink.stream.hls import HLSStream from streamlink.stream.http import HTTPStream from streamlink.utils.parse import parse_qsd log = logging.getLogger(__name__) class BrightcovePlayer: URL_PLAYER = "https://players.brightcove.net/{account_id}/{player_id}/index.html?videoId={video_id}" URL_API = "https://edge.api.brightcove.com/playback/v1/accounts/{account_id}/videos/{video_id}" def __init__(self, session, account_id, player_id="default_default"): self.session = session self.account_id = account_id self.player_id = player_id self.title = None log.debug(f"Creating player for account {account_id} (player_id={player_id})") def get_streams(self, video_id): log.debug(f"Finding streams for video: {video_id}") player_url = self.URL_PLAYER.format( account_id=self.account_id, player_id=self.player_id, video_id=video_id, ) policy_key = self.session.http.get( player_url, params={"videoId": video_id}, schema=validate.Schema( re.compile(r"""policyKey\s*:\s*(?P['"])(?P[\w-]+)(?P=q)"""), validate.any(None, validate.get("key")), ), ) if not policy_key: raise PluginError("Could not find Brightcove policy key") log.debug(f"Found policy key: {policy_key}") self.session.http.headers.update({"Referer": player_url}) sources, self.title = self.session.http.get( self.URL_API.format(account_id=self.account_id, video_id=video_id), headers={"Accept": f"application/json;pk={policy_key}"}, schema=validate.Schema( validate.parse_json(), { "sources": [ { "src": validate.url(), validate.optional("type"): str, validate.optional("container"): str, validate.optional("height"): int, validate.optional("avg_bitrate"): int, }, ], validate.optional("name"): str, }, validate.union_get("sources", "name"), ), ) for source in sources: if source.get("type") in ("application/vnd.apple.mpegurl", "application/x-mpegURL"): yield from HLSStream.parse_variant_playlist(self.session, source.get("src")).items() elif source.get("container") == "MP4": # determine quality name if source.get("height"): q = f"{source.get('height')}p" elif source.get("avg_bitrate"): q = f"{source.get('avg_bitrate') // 1000}k" else: q = "live" yield q, HTTPStream(self.session, source.get("src")) @pluginmatcher( re.compile(r"https?://players\.brightcove\.net/(?P[^/]+)/(?P[^/]+)/index\.html"), ) class Brightcove(Plugin): def _get_streams(self): video_id = parse_qsd(urlparse(self.url).query).get("videoId") player = BrightcovePlayer(self.session, self.match.group("account_id"), self.match.group("player_id")) streams = dict(player.get_streams(video_id)) self.title = player.title return streams __plugin__ = Brightcove ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/src/streamlink/plugins/btv.py0000644000175100001660000000355115003227510021062 0ustar00runnerdocker""" $description A privately owned Bulgarian live TV channel. $url btvplus.bg $type live $region Bulgaria """ import logging import re from streamlink.plugin import Plugin, pluginmatcher from streamlink.plugin.api import validate from streamlink.stream.hls import HLSStream log = logging.getLogger(__name__) @pluginmatcher( re.compile(r"https?://(?:www\.)?btvplus\.bg/live/?"), ) class BTV(Plugin): URL_API = "https://btvplus.bg/lbin/v3/btvplus/player_config.php" def _get_streams(self): media_id = self.session.http.get( self.url, schema=validate.Schema( re.compile(r"media_id=(\d+)"), validate.any(None, validate.get(1)), ), ) if media_id is None: return stream_url = self.session.http.get( self.URL_API, params={ "media_id": media_id, }, schema=validate.Schema( validate.any( validate.all( validate.regex(re.compile(r"geo_blocked_stream")), validate.get(0), ), validate.all( validate.parse_json(), { "status": "ok", "info": { "file": validate.url(path=validate.endswith(".m3u8")), }, }, validate.get(("info", "file")), ), ), ), ) if not stream_url: return if stream_url == "geo_blocked_stream": log.error("The content is not available in your region") return return HLSStream.parse_variant_playlist(self.session, stream_url) __plugin__ = BTV ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/src/streamlink/plugins/cbsnews.py0000644000175100001660000000270315003227510021731 0ustar00runnerdocker""" $description 24-hour live-streaming world news channel, based in the United States of America. $url cbsnews.com $type live $metadata id $metadata title """ import re from streamlink.plugin import Plugin, pluginmatcher from streamlink.plugin.api import validate from streamlink.stream.hls import HLSStream @pluginmatcher( re.compile(r"https?://(?:www\.)?cbsnews\.com/(?:\w+/)?live/?"), ) class CBSNews(Plugin): def _get_streams(self): data = self.session.http.get( self.url, schema=validate.Schema( re.compile(r"CBSNEWS\.defaultPayload\s*=\s*(\{.*?})\s*\n"), validate.none_or_all( validate.get(1), validate.parse_json(), { "items": [ { "id": str, "canonicalTitle": str, "video": validate.url(), "format": "application/x-mpegURL", }, ], }, validate.get(("items", 0)), validate.union_get("id", "canonicalTitle", "video"), ), ), ) if not data: return self.id, self.title, hls_url = data return HLSStream.parse_variant_playlist(self.session, hls_url) __plugin__ = CBSNews ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/src/streamlink/plugins/cdnbg.py0000644000175100001660000000771015003227510021345 0ustar00runnerdocker""" $description Bulgarian CDN hosting live content for various websites in Bulgaria. $url armymedia.bg $url bgonair.bg $url bloombergtv.bg $url bnt.bg $url live.bstv.bg $url i.cdn.bg $url nova.bg $url mu-vi.tv $type live $region Bulgaria """ import logging import re from urllib.parse import urlparse from streamlink.plugin import Plugin, pluginmatcher from streamlink.plugin.api import validate from streamlink.stream.hls import HLSStream from streamlink.utils.url import update_scheme log = logging.getLogger(__name__) @pluginmatcher( name="armymedia", pattern=re.compile(r"https?://(?:www\.)?armymedia\.bg/?"), ) @pluginmatcher( name="bgonair", pattern=re.compile(r"https?://(?:www\.)?bgonair\.bg/tvonline/?"), ) @pluginmatcher( name="bloombergtv", pattern=re.compile(r"https?://(?:www\.)?bloombergtv\.bg/video/?"), ) @pluginmatcher( name="bnt", pattern=re.compile(r"https?://(?:www\.)?(?:tv\.)?bnt\.bg/\w+(?:/\w+)?/?"), ) @pluginmatcher( name="bstv", pattern=re.compile(r"https?://(?:www\.)?live\.bstv\.bg/?"), ) @pluginmatcher( name="nova", pattern=re.compile(r"https?://(?:www\.)?nova\.bg/live/?"), ) @pluginmatcher( name="mu-vi", pattern=re.compile(r"https?://(?:www\.)?mu-vi\.tv/LiveStreams/pages/Live\.aspx/?"), ) @pluginmatcher( name="cdnbg", pattern=re.compile(r"https?://(?:www\.)?i\.cdn\.bg/live/?"), ) class CDNBG(Plugin): @staticmethod def _find_url(regex: re.Pattern) -> validate.all: return validate.all( validate.regex(regex), validate.get("url"), ) def _get_streams(self): if "cdn.bg" in urlparse(self.url).netloc: iframe_url = self.url h = self.session.get_option("http-headers") if not h or not h.get("Referer"): log.error('Missing Referer for iframe URL, use --http-header "Referer=URL" ') return referer = h.get("Referer") else: referer = self.url iframe_url = self.session.http.get( self.url, schema=validate.Schema( validate.any( self._find_url( re.compile(r"'src',\s*'(?Phttps?://i\.cdn\.bg/live/\w+)'\);"), ), validate.all( validate.parse_html(), validate.xml_xpath_string(".//iframe[contains(@src,'cdn.bg')][1]/@src"), ), ), ), ) if not iframe_url: return iframe_url = update_scheme("https://", iframe_url, force=False) log.debug(f"Found iframe: {iframe_url}") stream_url = self.session.http.get( iframe_url, headers={"Referer": referer}, schema=validate.Schema( validate.any( self._find_url( re.compile(r"sdata\.src.*?=.*?(?P[\"'])(?P.*?)(?P=q)"), ), self._find_url( re.compile(r"(src|file): (?P[\"'])(?P(https?:)?//.+?m3u8.*?)(?P=q)"), ), self._find_url( re.compile(r"video src=(?Phttp[^ ]+m3u8[^ ]*)"), ), self._find_url( re.compile(r"source src=\"(?P[^\"]+m3u8[^\"]*)\""), ), # GEOBLOCKED self._find_url( re.compile(r"(?P[^\"]+geoblock[^\"]+)"), ), ), ), ) if "geoblock" in stream_url: log.error("Geo-restricted content") return return HLSStream.parse_variant_playlist( self.session, update_scheme(iframe_url, stream_url), headers={"Referer": "https://i.cdn.bg/"}, ) __plugin__ = CDNBG ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/src/streamlink/plugins/ceskatelevize.py0000644000175100001660000001140415003227510023121 0ustar00runnerdocker""" $description Live TV channels from CT, a Czech public, state-owned broadcaster. $url ceskatelevize.cz $type live $metadata id $metadata title $region Czechia """ import logging import re from uuid import uuid4 from streamlink.plugin import Plugin, pluginmatcher from streamlink.plugin.api import validate from streamlink.stream.dash import DASHStream log = logging.getLogger(__name__) @pluginmatcher( name="channel", pattern=re.compile(r"https?://(?Pct24|decko)\.ceskatelevize\.cz/"), ) @pluginmatcher( name="sport", pattern=re.compile(r"https?://sport\.ceskatelevize\.cz/"), ) @pluginmatcher( name="default", pattern=re.compile(r"https?://(?:www\.)?ceskatelevize\.cz/zive/\w+"), ) class Ceskatelevize(Plugin): URL_API_CHANNEL = "https://api.ceskatelevize.cz/video/v1/playlist-live/v1/stream-data/channel/{channel}" CHANNELS = { "ct24": "CH_24", "decko": "CH_5", } def get_streams_from_api(self, channel): if not channel: return self.title, is_blocked, url = self.session.http.get( self.URL_API_CHANNEL.format(channel=channel), params={ "canPlayDRM": "false", "streamType": "dash", "quality": "web", "maxQualityCount": 5, "sessionId": uuid4(), }, schema=validate.Schema( validate.parse_json(), { "streamUrls": { "isBlocked": bool, "main": validate.url(), }, "title": str, }, validate.union_get( "title", ("streamUrls", "isBlocked"), ("streamUrls", "main"), ), ), ) if is_blocked: log.error("The stream is inaccessible") return return url def get_streams_channel(self): self.id = self.CHANNELS.get(self.match["channel"]) return self.get_streams_from_api(self.id) def get_streams_sport(self): schema = validate.Schema( validate.parse_html(), validate.xml_xpath_string(".//section[@id='live']/@data-ctcomp-data"), validate.none_or_all( validate.parse_json(), { "items": [ { "items": [ { validate.optional("video"): { "data": { "source": { "playlist": [ { "id": str, "drm": int, }, ], }, }, }, }, ], }, ], }, validate.get(("items", 0, "items", 0, "video", "data", "source", "playlist", 0, "id")), ), ) self.id = self.session.http.get(self.url, schema=schema) return self.get_streams_from_api(self.id) def get_streams_default(self): schema = validate.Schema( validate.parse_html(), validate.xml_xpath_string(".//script[@id='__NEXT_DATA__'][text()]/text()"), str, validate.parse_json(), { "props": { "pageProps": { validate.optional("data"): validate.all( { "liveBroadcast": { "id": str, }, }, validate.get(("liveBroadcast", "id")), ), }, }, }, validate.get(("props", "pageProps", "data")), ) self.id = self.session.http.get(self.url, schema=schema) return self.get_streams_from_api(self.id) def _get_streams(self): if self.matches["channel"]: url = self.get_streams_channel() elif self.matches["sport"]: url = self.get_streams_sport() else: url = self.get_streams_default() if not url: return return DASHStream.parse_manifest(self.session, url) __plugin__ = Ceskatelevize ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/src/streamlink/plugins/chzzk.py0000644000175100001660000002021315003227510021412 0ustar00runnerdocker""" $description South Korean live-streaming platform for gaming, entertainment, and other creative content. Owned by Naver. $url chzzk.naver.com $type live, vod $metadata id $metadata author $metadata category $metadata title """ import logging import re from streamlink.plugin import Plugin, pluginmatcher from streamlink.plugin.api import validate from streamlink.stream.dash import DASHStream from streamlink.stream.hls import HLSStream log = logging.getLogger(__name__) class ChzzkAPI: _CHANNELS_LIVE_DETAIL_URL = "https://api.chzzk.naver.com/service/v2/channels/{channel_id}/live-detail" _VIDEOS_URL = "https://api.chzzk.naver.com/service/v2/videos/{video_id}" _CLIP_URL = "https://api.chzzk.naver.com/service/v1/play-info/clip/{clip_id}" def __init__(self, session): self._session = session def _query_api(self, url, *schemas): return self._session.http.get( url, acceptable_status=(200, 400, 404), schema=validate.Schema( validate.parse_json(), validate.any( validate.all( { "code": int, "message": str, }, validate.transform(lambda data: ("error", data["message"])), ), validate.all( { "code": 200, "content": None, }, validate.transform(lambda _: ("success", None)), ), validate.all( { "code": 200, "content": dict, }, validate.get("content"), *schemas, validate.transform(lambda data: ("success", data)), ), ), ), ) def get_live_detail(self, channel_id): return self._query_api( self._CHANNELS_LIVE_DETAIL_URL.format(channel_id=channel_id), { "status": str, "liveId": int, "liveTitle": validate.any(str, None), "liveCategory": validate.any(str, None), "adult": bool, "channel": validate.all( {"channelName": str}, validate.get("channelName"), ), "livePlaybackJson": validate.none_or_all( str, validate.parse_json(), { "media": [ validate.all( { "mediaId": str, "protocol": str, "path": validate.url(), }, validate.union_get( "mediaId", "protocol", "path", ), ), ], }, validate.get("media"), ), }, validate.union_get( "livePlaybackJson", "status", "liveId", "channel", "liveCategory", "liveTitle", "adult", ), ) def get_videos(self, video_id): return self._query_api( self._VIDEOS_URL.format(video_id=video_id), { "inKey": validate.any(str, None), "videoNo": validate.any(int, None), "videoId": validate.any(str, None), "videoTitle": validate.any(str, None), "videoCategory": validate.any(str, None), "adult": bool, "channel": validate.all( {"channelName": str}, validate.get("channelName"), ), }, validate.union_get( "adult", "inKey", "videoId", "videoNo", "channel", "videoTitle", "videoCategory", ), ) def get_clips(self, clip_id): return self._query_api( self._CLIP_URL.format(clip_id=clip_id), { validate.optional("inKey"): validate.any(str, None), validate.optional("videoId"): validate.any(str, None), "contentId": str, "contentTitle": str, "adult": bool, "ownerChannel": validate.all( {"channelName": str}, validate.get("channelName"), ), }, validate.union(( validate.get("adult"), validate.get("inKey"), validate.get("videoId"), validate.get("contentId"), validate.get("ownerChannel"), validate.get("contentTitle"), validate.transform(lambda _: None), )), ) @pluginmatcher( name="live", pattern=re.compile( r"https?://chzzk\.naver\.com/live/(?P[^/?]+)", ), ) @pluginmatcher( name="video", pattern=re.compile( r"https?://chzzk\.naver\.com/video/(?P[^/?]+)", ), ) @pluginmatcher( name="clip", pattern=re.compile( r"https?://chzzk\.naver\.com/clips/(?P[^/?]+)", ), ) class Chzzk(Plugin): _API_VOD_PLAYBACK_URL = "https://apis.naver.com/neonplayer/vodplay/v2/playback/{video_id}?key={in_key}" _STATUS_OPEN = "OPEN" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._api = ChzzkAPI(self.session) def _get_live(self, channel_id): datatype, data = self._api.get_live_detail(channel_id) if datatype == "error": log.error(data) return if data is None: return media, status, self.id, self.author, self.category, self.title, adult = data if status != self._STATUS_OPEN: log.error("The stream is unavailable") return if media is None: log.error(f"This stream is {'for adults only' if adult else 'unavailable'}") return for media_id, media_protocol, media_path in media: if media_protocol == "HLS" and media_id == "HLS": return HLSStream.parse_variant_playlist( self.session, media_path, channel_id=channel_id, ffmpeg_options={"copyts": True}, ) def _get_vod_playback(self, datatype, data): if datatype == "error": log.error(data) return adult, in_key, vod_id, *metadata = data if in_key is None or vod_id is None: log.error(f"This stream is {'for adults only' if adult else 'unavailable'}") return self.id, self.author, self.title, self.category = metadata for name, stream in DASHStream.parse_manifest( self.session, self._API_VOD_PLAYBACK_URL.format(video_id=vod_id, in_key=in_key), headers={"Accept": "application/dash+xml"}, ).items(): if stream.video_representation.mimeType == "video/mp2t": yield name, stream def _get_video(self, video_id): return self._get_vod_playback( *self._api.get_videos(video_id), ) def _get_clip(self, clip_id): return self._get_vod_playback( *self._api.get_clips(clip_id), ) def _get_streams(self): if self.matches["live"]: return self._get_live(self.match["channel_id"]) elif self.matches["video"]: return self._get_video(self.match["video_id"]) elif self.matches["clip"]: return self._get_clip(self.match["clip_id"]) __plugin__ = Chzzk ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/src/streamlink/plugins/cinergroup.py0000644000175100001660000000437215003227510022446 0ustar00runnerdocker""" $description Turkish live TV channels from Ciner Group, including Haberturk TV and Show TV. $url bloomberght.com $url haberturk.com $url haberturk.tv $url showmax.com.tr $url showturk.com.tr $url showtv.com.tr $type live """ import re from streamlink.plugin import Plugin, pluginmatcher from streamlink.plugin.api import validate from streamlink.stream.hls import HLSStream @pluginmatcher( name="bloomberght", pattern=re.compile(r"https?://(?:www\.)?bloomberght\.com/tv/?"), ) @pluginmatcher( name="haberturk", pattern=re.compile(r"https?://(?:www\.)?haberturk\.(?:com|tv)(?:/tv)?/canliyayin/?"), ) @pluginmatcher( name="showmax", pattern=re.compile(r"https?://(?:www\.)?showmax\.com\.tr/canli-?yayin/?"), ) @pluginmatcher( name="showturk", pattern=re.compile(r"https?://(?:www\.)?showturk\.com\.tr/canli-?yayin(?:/showtv)?/?"), ) @pluginmatcher( name="showtv", pattern=re.compile(r"https?://(?:www\.)?showtv\.com\.tr/canli-yayin(?:/showtv)?/?"), ) class CinerGroup(Plugin): @staticmethod def _schema_videourl(): return validate.Schema( validate.xml_xpath_string(".//script[contains(text(), 'videoUrl')]/text()"), validate.none_or_all( re.compile(r"""(?['"])(?P.+?)(?P=q)"""), validate.none_or_all( validate.get("url"), validate.url(), ), ), ) @staticmethod def _schema_data_ht(): return validate.Schema( validate.xml_xpath_string(".//div[@data-ht][1]/@data-ht"), validate.none_or_all( validate.parse_json(), { "ht_stream_m3u8": validate.url(), }, validate.get("ht_stream_m3u8"), ), ) def _get_streams(self): root = self.session.http.get(self.url, schema=validate.Schema(validate.parse_html())) schema_getters = self._schema_videourl, self._schema_data_ht stream_url = next((res for res in (getter().validate(root) for getter in schema_getters) if res), None) if stream_url: return HLSStream.parse_variant_playlist(self.session, stream_url) __plugin__ = CinerGroup ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1745694536.0 streamlink-7.3.0/src/streamlink/plugins/clubbingtv.py0000644000175100001660000000475415003227510022434 0ustar00runnerdocker""" $description Television channel dedicated to electronic music, DJs and dance music culture. $url clubbingtv.com $type live, vod $account Login required """ import logging import re from streamlink.plugin import Plugin, pluginargument, pluginmatcher from streamlink.stream.hls import HLSStream log = logging.getLogger(__name__) @pluginmatcher( re.compile(r"https?://(www\.)?clubbingtv\.com/"), ) @pluginargument( "username", required=True, requires=["password"], help="The username used to register with Clubbing TV.", ) @pluginargument( "password", required=True, sensitive=True, help="A Clubbing TV account password to use with --clubbingtv-username.", ) class ClubbingTV(Plugin): _login_url = "https://www.clubbingtv.com/user/login" _live_re = re.compile( r'playerInstance\.setup\({\s*"file"\s*:\s*"(?P.+?)"', re.DOTALL, ) _vod_re = re.compile(r'