Skip to content

Conversation

@bastimeyer
Copy link
Member

@bastimeyer bastimeyer commented Apr 9, 2025

Resolves #6449

These are unfinished changes... The implementation works and should mostly be fine, apart from stuff that may come up while finishing and cleaning this up. Most tests however still need to be done (the CI status checks will therefore currently fail, tests and typing stuff). I still decided to open this PR, because this helps me finding potential issues.

The PR branch is currently based on #6496, because this branch hasn't been merged yet. Once merged, I'm going to rebase here.


  1. Implement status messages on the console output via ConsoleOutput.msg_status()
    • Add base line-buffered ConsoleOutputStream(StreamWrapper) without status message support
    • Add ConsoleOutputStreamANSI with status message support via ANSI control sequences
    • Check for available support and choose the right class automatically when initializing
  2. Implement WindowsConsole and ConsoleOutputStreamWindows
    • Check whether "virtual terminal processing" is supported (ANSI control sequences), which is the case since newer versions of PowerShell and the Windows Terminal
    • If it's not supported, then use the classic Windows Console APIs (deprecated, but supported indefinitely)
  3. Refactor Process and StreamRunner
    • Make Progress call the newly added ConsoleOutput.msg_status() instead of writing to stderr and adding whitespace padding to each message
    • Initialize the Progress instance outside of StreamRunner
  4. Make --progress=force write regular console output messages in a slower interval when not a TTY. This restores and improves the old behavior.

This assumes that ANSI control sequence processing is supported by each terminal application on non-Windows unless the env vars TERM=dumb or TERM=unknown are set. That feature assumption should be fine though.

The docs of the Windows Console API calls are all linked in the implementation.

One issue I noticed while testing this in my Windows 10 VM was while using the mintty terminal from git-for-windows (MinGW / MSYS2). Apparently, isatty() calls always return false because the stdio streams are wrapped. This means that status messages won't work here. The workaround for that is wrapping the command in winpty (winpty streamlink ...). This is a known issue in this env and git even implements its own workarounds in its code, according to my research. This isn't feasible for Streamlink though and should be done by CPython anyway, so I don't care.

As commented in the last commit, --progress=force now doesn't make any sense anymore, as the support for status messages needs to be checked at runtime and can't be forced. Previously we simply wrote status messages to stderr and added whitespace padding, so forcing status messages on non-interactive stdio streams made sense. --progress=force now could be changed so that it'll make Progress write non-status-messages to the non-interactive console output, but in a slower interval, so that it doesn't spam that many messages.

@bastimeyer bastimeyer added CLI WIP Work in process labels Apr 9, 2025
@bastimeyer bastimeyer force-pushed the cli/console/status branch 3 times, most recently from 05c28f8 to 69d2810 Compare April 11, 2025 12:09
@bastimeyer bastimeyer force-pushed the cli/console/status branch 2 times, most recently from 372b803 to d6c0c88 Compare April 11, 2025 20:06
- Add `ConsoleOutput.msg_status()` method for writing status messages
  to the console output, if supported by the output stream
- Implement line-buffered `ConsoleOutputStream` which handles all
  writes to the console output and which doesn't support status messages
- Implement `ConsoleOutputStreamANSI` with support for status messages
  via ANSI control sequences
- Add feature detection to the `ConsoleOutputStream` constructor,
  so the right class is instantiated automatically

Next: Add support for the "classic Windows Console" APIs
Add support for "classic Windows Console" APIs that allow
the implementation of status messages without ANSI control sequences.
- Pass the `ConsoleOutput` instance to `Progress`, as well as
  the output file path, and pass an optional `Progress` instance
  to `StreamRunner` instead of instantiating it there
- Make `Progress` call the newly added `ConsoleOutput.msg_status()`
  method for each progress status update message instead of writing
  messages to `stderr` and adding whitespace padding to each line

NEXT:
If `--progress=force` is set, make `Progress` write non-status messages
to the console output in a slower interval
Restore the `--progress=force` functionality by making it write
non-status messages to the console output if the console output
doesn't support status messages (not a TTY stream). In this case,
increase the update interval from 0.25 seconds to 2 seconds, so the
regular console output doesn't get spammed by the download progress.
@bastimeyer bastimeyer marked this pull request as ready for review April 12, 2025 11:15
@bastimeyer bastimeyer removed the WIP Work in process label Apr 12, 2025
@bastimeyer
Copy link
Member Author

This should be finished now. The other PR which this one here was based on has been merged and I've rebased onto master. I'm going to have a couple more checks, then I'm going to merge this one as well.

Few more notes:

  • The ConsoleOutputStream doesn't remove any ANSI control sequences from the output, which means that if certain control sequences get written to the output (remember we're wrapping the entire sys.stdout / sys.stderr stream, so it affects everything that doesn't explicitly keep an object ref from before the wrapping or that writes to sys.__stdout__/sys.__stderr__ directly), this can mess up the status line. That is currently not a problem though, so for now it's fine.
  • The Windows Terminal still has issues with clearing the text line after a carriage return character while resizing the terminal window. I already commented on this when I rewrote the progress output formatting a few years ago, as it's aware of its available space. This apparently is not an issue when using the "classic Windows Console" APIs, but that doesn't justify using those deprecated APIs at all times on Windows systems.

Examples

master - stdout logs, stderr progress with whitespace padding

$ streamlink --no-config -l debug -o /dev/null twitch.tv/esl_dota2 best
[cli][debug] OS:         Linux-6.14.0-1-git-x86_64-with-glibc2.41
[cli][debug] Python:     3.13.2
[cli][debug] OpenSSL:    OpenSSL 3.4.1 11 Feb 2025
[cli][debug] Streamlink: 7.2.0+7.gb6c65ed6
[cli][debug] Dependencies:
[cli][debug]  certifi: 2024.12.14
[cli][debug]  isodate: 0.7.2
[cli][debug]  lxml: 5.3.0
[cli][debug]  pycountry: 24.6.1
[cli][debug]  pycryptodome: 3.21.0
[cli][debug]  PySocks: 1.7.1
[cli][debug]  requests: 2.32.3
[cli][debug]  trio: 0.27.0
[cli][debug]  trio-websocket: 0.11.1
[cli][debug]  urllib3: 2.3.0
[cli][debug]  websocket-client: 1.8.0
[cli][debug] Arguments:
[cli][debug]  url=twitch.tv/esl_dota2
[cli][debug]  stream=['best']
[cli][debug]  --no-config=True
[cli][debug]  --loglevel=debug
[cli][debug]  --output=/dev/null
[cli][info] Found matching plugin twitch for URL twitch.tv/esl_dota2
[plugins.twitch][debug] Getting live HLS streams for esl_dota2
[plugins.twitch][debug] {'adblock': False, 'geoblock_reason': '', 'hide_ads': False, 'server_ads': True, 'show_ads': True}
[utils.l10n][debug] Language code: en_US
[cli][info] Available streams: audio_only, 160p (worst), 360p, 480p, 720p60, 1080p60 (best)
[cli][info] Opening stream: 1080p60 (hls)
[cli][info] Writing output to
/dev/null
[cli][debug] Checking file output
[stream.hls][debug] Reloading playlist
[cli][debug] Pre-buffering 8192 bytes
[stream.hls][debug] First Sequence: 15876; Last Sequence: 15890
[stream.hls][debug] Start offset: 0; Duration: None; Start Sequence: 15888; End Sequence: None
[stream.hls][debug] Adding segment 15888 to queue
[stream.hls][debug] Adding segment 15889 to queue
[stream.hls][debug] Adding segment 15890 to queue
[stream.hls][debug] Writing segment 15888 to output
[stream.hls][debug] Segment 15888 complete
[cli][debug] Writing stream to output
[stream.hls][debug] Writing segment 15889 to output
[stream.hls][debug] Segment 15889 complete
[stream.hls][debug] Writing segment 15890 to output
[stream.hls][debug] Segment 15890 complete
[download] Written 4.59 MiB to /dev/null (5s @ 896.12 KiB/s)                                                                                    [stream.hls][debug] Reloading playlist
[stream.hls][debug] Adding segment 15891 to queue
[stream.hls][debug] Adding segment 15892 to queue
[stream.hls][debug] Adding segment 15893 to queue
[stream.hls][debug] Writing segment 15891 to output
[stream.hls][debug] Segment 15891 complete
[download] Written 6.13 MiB to /dev/null (5s @ 1.11 MiB/s)                                                                                      [stream.hls][debug] Writing segment 15892 to output
[stream.hls][debug] Segment 15892 complete
[stream.hls][debug] Writing segment 15893 to output
[stream.hls][debug] Segment 15893 complete
[download] Written 9.17 MiB to /dev/null (11s @ 834.42 KiB/s)                                                                                   [stream.hls][debug] Reloading playlist
[stream.hls][debug] Adding segment 15894 to queue
[stream.hls][debug] Adding segment 15895 to queue
[stream.hls][debug] Adding segment 15896 to queue
[stream.hls][debug] Writing segment 15894 to output
[stream.hls][debug] Segment 15894 complete
[download] Written 10.69 MiB to /dev/null (11s @ 951.97 KiB/s)                                                                                  [stream.hls][debug] Writing segment 15895 to output
[stream.hls][debug] Segment 15895 complete
[stream.hls][debug] Writing segment 15896 to output
[stream.hls][debug] Segment 15896 complete
[download] Written 13.76 MiB to /dev/null (12s @ 1.17 MiB/s)                                                                                    [download] Written 13.76 MiB to /dev/null (12s @ 1.14 MiB/s)                                                                                    
[stream.segmented][debug] Closing worker thread
[stream.segmented][debug] Closing writer thread
[cli][info] Stream ended
Interrupted! Exiting...
[cli][info] Closing currently open stream...

PR - sticky download progress status line at the bottom

$ streamlink --no-config -l debug -o /dev/null twitch.tv/esl_dota2 best
[cli][debug] OS:         Linux-6.14.0-1-git-x86_64-with-glibc2.41
[cli][debug] Python:     3.13.2
[cli][debug] OpenSSL:    OpenSSL 3.4.1 11 Feb 2025
[cli][debug] Streamlink: 7.2.0+11.gfdef0703
[cli][debug] Dependencies:
[cli][debug]  certifi: 2024.12.14
[cli][debug]  isodate: 0.7.2
[cli][debug]  lxml: 5.3.0
[cli][debug]  pycountry: 24.6.1
[cli][debug]  pycryptodome: 3.21.0
[cli][debug]  PySocks: 1.7.1
[cli][debug]  requests: 2.32.3
[cli][debug]  trio: 0.27.0
[cli][debug]  trio-websocket: 0.11.1
[cli][debug]  urllib3: 2.3.0
[cli][debug]  websocket-client: 1.8.0
[cli][debug] Arguments:
[cli][debug]  url=twitch.tv/esl_dota2
[cli][debug]  stream=['best']
[cli][debug]  --no-config=True
[cli][debug]  --loglevel=debug
[cli][debug]  --output=/dev/null
[cli][info] Found matching plugin twitch for URL twitch.tv/esl_dota2
[plugins.twitch][debug] Getting live HLS streams for esl_dota2
[plugins.twitch][debug] {'adblock': False, 'geoblock_reason': '', 'hide_ads': False, 'server_ads': True, 'show_ads': True}
[utils.l10n][debug] Language code: en_US
[cli][info] Available streams: audio_only, 160p (worst), 360p, 480p, 720p60, 1080p60 (best)
[cli][info] Opening stream: 1080p60 (hls)
[cli][info] Writing output to
/dev/null
[cli][debug] Checking file output
[stream.hls][debug] Reloading playlist
[cli][debug] Pre-buffering 8192 bytes
[stream.hls][debug] First Sequence: 15749; Last Sequence: 15763
[stream.hls][debug] Start offset: 0; Duration: None; Start Sequence: 15761; End Sequence: None
[stream.hls][debug] Adding segment 15761 to queue
[stream.hls][debug] Adding segment 15762 to queue
[stream.hls][debug] Adding segment 15763 to queue
[stream.hls][debug] Writing segment 15761 to output
[stream.hls][debug] Segment 15761 complete
[cli][debug] Writing stream to output
[stream.hls][debug] Writing segment 15762 to output
[stream.hls][debug] Segment 15762 complete
[stream.hls][debug] Writing segment 15763 to output
[stream.hls][debug] Segment 15763 complete
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Adding segment 15764 to queue
[stream.hls][debug] Adding segment 15765 to queue
[stream.hls][debug] Adding segment 15766 to queue
[stream.hls][debug] Writing segment 15764 to output
[stream.hls][debug] Segment 15764 complete
[stream.hls][debug] Writing segment 15765 to output
[stream.hls][debug] Segment 15765 complete
[stream.hls][debug] Writing segment 15766 to output
[stream.hls][debug] Segment 15766 complete
[stream.segmented][debug] Closing worker thread
[stream.segmented][debug] Closing writer thread
[cli][info] Stream ended
Interrupted! Exiting...
[cli][info] Closing currently open stream...
[download] Written 9.20 MiB to /dev/null (7s @ 1.27 MiB/s)

PR - no status line with download progress if not a TTY

(SIGINT on cat causes SIGPIPE on streamlink, which is handled correctly, hence no "stream ended" logs, etc)

$ streamlink --no-config -l debug -o /dev/null twitch.tv/esl_dota2 best | cat
[cli][debug] OS:         Linux-6.14.0-1-git-x86_64-with-glibc2.41
[cli][debug] Python:     3.13.2
[cli][debug] OpenSSL:    OpenSSL 3.4.1 11 Feb 2025
[cli][debug] Streamlink: 7.2.0+11.gfdef0703
[cli][debug] Dependencies:
[cli][debug]  certifi: 2024.12.14
[cli][debug]  isodate: 0.7.2
[cli][debug]  lxml: 5.3.0
[cli][debug]  pycountry: 24.6.1
[cli][debug]  pycryptodome: 3.21.0
[cli][debug]  PySocks: 1.7.1
[cli][debug]  requests: 2.32.3
[cli][debug]  trio: 0.27.0
[cli][debug]  trio-websocket: 0.11.1
[cli][debug]  urllib3: 2.3.0
[cli][debug]  websocket-client: 1.8.0
[cli][debug] Arguments:
[cli][debug]  url=twitch.tv/esl_dota2
[cli][debug]  stream=['best']
[cli][debug]  --no-config=True
[cli][debug]  --loglevel=debug
[cli][debug]  --output=/dev/null
[cli][info] Found matching plugin twitch for URL twitch.tv/esl_dota2
[plugins.twitch][debug] Getting live HLS streams for esl_dota2
[plugins.twitch][debug] {'adblock': False, 'geoblock_reason': '', 'hide_ads': False, 'server_ads': True, 'show_ads': True}
[utils.l10n][debug] Language code: en_US
[cli][info] Available streams: audio_only, 160p (worst), 360p, 480p, 720p60, 1080p60 (best)
[cli][info] Opening stream: 1080p60 (hls)
[cli][info] Writing output to
/dev/null
[cli][debug] Checking file output
[stream.hls][debug] Reloading playlist
[cli][debug] Pre-buffering 8192 bytes
[stream.hls][debug] First Sequence: 15770; Last Sequence: 15784
[stream.hls][debug] Start offset: 0; Duration: None; Start Sequence: 15782; End Sequence: None
[stream.hls][debug] Adding segment 15782 to queue
[stream.hls][debug] Adding segment 15783 to queue
[stream.hls][debug] Adding segment 15784 to queue
[stream.hls][debug] Writing segment 15782 to output
[stream.hls][debug] Segment 15782 complete
[cli][debug] Writing stream to output
[stream.hls][debug] Writing segment 15783 to output
[stream.hls][debug] Segment 15783 complete
[stream.hls][debug] Writing segment 15784 to output
[stream.hls][debug] Segment 15784 complete
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Adding segment 15785 to queue
[stream.hls][debug] Adding segment 15786 to queue
[stream.hls][debug] Adding segment 15787 to queue
[stream.hls][debug] Writing segment 15785 to output
[stream.hls][debug] Segment 15785 complete
[stream.hls][debug] Writing segment 15786 to output
[stream.hls][debug] Segment 15786 complete
[stream.hls][debug] Writing segment 15787 to output
[stream.hls][debug] Segment 15787 complete
^C

PR - force download progress on non-TTY stream

$ streamlink --no-config --progress=force -l debug -o /dev/null twitch.tv/esl_dota2 best | cat
[cli][debug] OS:         Linux-6.14.0-1-git-x86_64-with-glibc2.41
[cli][debug] Python:     3.13.2
[cli][debug] OpenSSL:    OpenSSL 3.4.1 11 Feb 2025
[cli][debug] Streamlink: 7.2.0+11.gfdef0703
[cli][debug] Dependencies:
[cli][debug]  certifi: 2024.12.14
[cli][debug]  isodate: 0.7.2
[cli][debug]  lxml: 5.3.0
[cli][debug]  pycountry: 24.6.1
[cli][debug]  pycryptodome: 3.21.0
[cli][debug]  PySocks: 1.7.1
[cli][debug]  requests: 2.32.3
[cli][debug]  trio: 0.27.0
[cli][debug]  trio-websocket: 0.11.1
[cli][debug]  urllib3: 2.3.0
[cli][debug]  websocket-client: 1.8.0
[cli][debug] Arguments:
[cli][debug]  url=twitch.tv/esl_dota2
[cli][debug]  stream=['best']
[cli][debug]  --no-config=True
[cli][debug]  --loglevel=debug
[cli][debug]  --output=/dev/null
[cli][debug]  --progress=force
[cli][info] Found matching plugin twitch for URL twitch.tv/esl_dota2
[plugins.twitch][debug] Getting live HLS streams for esl_dota2
[plugins.twitch][debug] {'adblock': False, 'geoblock_reason': '', 'hide_ads': False, 'server_ads': True, 'show_ads': True}
[utils.l10n][debug] Language code: en_US
[cli][info] Available streams: audio_only, 160p (worst), 360p, 480p, 720p60, 1080p60 (best)
[cli][info] Opening stream: 1080p60 (hls)
[cli][info] Writing output to
/dev/null
[cli][debug] Checking file output
[stream.hls][debug] Reloading playlist
[cli][debug] Pre-buffering 8192 bytes
[stream.hls][debug] First Sequence: 15834; Last Sequence: 15848
[stream.hls][debug] Start offset: 0; Duration: None; Start Sequence: 15846; End Sequence: None
[stream.hls][debug] Adding segment 15846 to queue
[stream.hls][debug] Adding segment 15847 to queue
[stream.hls][debug] Adding segment 15848 to queue
[stream.hls][debug] Writing segment 15846 to output
[stream.hls][debug] Segment 15846 complete
[cli][debug] Writing stream to output
[stream.hls][debug] Writing segment 15847 to output
[stream.hls][debug] Segment 15847 complete
[stream.hls][debug] Writing segment 15848 to output
[stream.hls][debug] Segment 15848 complete
[download] Written 4.59 MiB to /dev/null (2s)
[download] Written 4.59 MiB to /dev/null (4s @ 2.29 MiB/s)
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Adding segment 15849 to queue
[stream.hls][debug] Adding segment 15850 to queue
[stream.hls][debug] Adding segment 15851 to queue
[stream.hls][debug] Writing segment 15849 to output
[stream.hls][debug] Segment 15849 complete
[stream.hls][debug] Writing segment 15850 to output
[stream.hls][debug] Segment 15850 complete
[stream.hls][debug] Writing segment 15851 to output
[stream.hls][debug] Segment 15851 complete
[download] Written 9.21 MiB to /dev/null (6s @ 2.30 MiB/s)
[download] Written 9.21 MiB to /dev/null (8s @ 1.53 MiB/s)
[download] Written 9.21 MiB to /dev/null (10s @ 1.15 MiB/s)
^C

@bastimeyer
Copy link
Member Author

Windows Terminal, BASH from git-on-Windows (MinGW64 env)

Uses ANSI control sequences

$ streamlink --no-config -l debug -o /dev/null twitch.tv/esl_dota2 best
[cli][debug] OS:         Windows 10
[cli][debug] Python:     3.13.0a6
[cli][debug] OpenSSL:    OpenSSL 3.0.13 30 Jan 2024
[cli][debug] Streamlink: 7.2.0+11.gfdef0703
[cli][debug] Dependencies:
[cli][debug]  certifi: 2024.2.2
[cli][debug]  exceptiongroup: 1.2.1
[cli][debug]  isodate: 0.6.1
[cli][debug]  lxml: 5.2.1
[cli][debug]  pycountry: 23.12.11
[cli][debug]  pycryptodome: 3.20.0
[cli][debug]  PySocks: 1.7.1
[cli][debug]  requests: 2.31.0
[cli][debug]  trio: 0.25.0
[cli][debug]  trio-websocket: 0.11.1
[cli][debug]  urllib3: 2.2.1
[cli][debug]  websocket-client: 1.8.0
[cli][debug] Arguments:
[cli][debug]  url=twitch.tv/esl_dota2
[cli][debug]  stream=['best']
[cli][debug]  --no-config=True
[cli][debug]  --loglevel=debug
[cli][debug]  --output=nul
[cli][info] Found matching plugin twitch for URL twitch.tv/esl_dota2
[plugins.twitch][debug] Getting live HLS streams for esl_dota2
[plugins.twitch][debug] {'adblock': False, 'geoblock_reason': '', 'hide_ads': False, 'server_ads': True, 'show_ads': True}
[utils.l10n][debug] Language code: en_US
[cli][info] Available streams: audio_only, 160p (worst), 360p, 480p, 720p60, 1080p60 (best)
[cli][info] Opening stream: 1080p60 (hls)
[cli][info] Writing output to
\\.\NUL
[cli][debug] Checking file output
[stream.hls][debug] Reloading playlist
[cli][debug] Pre-buffering 8192 bytes
[stream.hls][debug] First Sequence: 16005; Last Sequence: 16019
[stream.hls][debug] Start offset: 0; Duration: None; Start Sequence: 16017; End Sequence: None
[stream.hls][debug] Adding segment 16017 to queue
[stream.hls][debug] Adding segment 16018 to queue
[stream.hls][debug] Adding segment 16019 to queue
[stream.hls][debug] Writing segment 16017 to output
[stream.hls][debug] Segment 16017 complete
[cli][debug] Writing stream to output
[stream.hls][debug] Writing segment 16018 to output
[stream.hls][debug] Segment 16018 complete
[stream.hls][debug] Writing segment 16019 to output
[stream.hls][debug] Segment 16019 complete
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Adding segment 16020 to queue
[stream.hls][debug] Adding segment 16021 to queue
[stream.hls][debug] Adding segment 16022 to queue
[stream.hls][debug] Writing segment 16020 to output
[stream.hls][debug] Segment 16020 complete
[stream.segmented][debug] Closing worker thread
[stream.segmented][debug] Closing writer thread
[stream.hls][debug] Writing segment 16021 to output
[stream.hls][debug] Segment 16021 complete
[cli][info] Stream ended
Interrupted! Exiting...
[cli][info] Closing currently open stream...
[download] Written 4.57 MiB to \\.\NUL (5s @ 909.72 KiB/s)

Windows Terminal, PowerShell 7

Uses ANSI control sequences

$ streamlink --no-config -l debug -o NUL twitch.tv/esl_dota2 best
[cli][debug] OS:         Windows 10
[cli][debug] Python:     3.13.0a6
[cli][debug] OpenSSL:    OpenSSL 3.0.13 30 Jan 2024
[cli][debug] Streamlink: 7.2.0+11.gfdef0703
[cli][debug] Dependencies:
[cli][debug]  certifi: 2024.2.2
[cli][debug]  exceptiongroup: 1.2.1
[cli][debug]  isodate: 0.6.1
[cli][debug]  lxml: 5.2.1
[cli][debug]  pycountry: 23.12.11
[cli][debug]  pycryptodome: 3.20.0
[cli][debug]  PySocks: 1.7.1
[cli][debug]  requests: 2.31.0
[cli][debug]  trio: 0.25.0
[cli][debug]  trio-websocket: 0.11.1
[cli][debug]  urllib3: 2.2.1
[cli][debug]  websocket-client: 1.8.0
[cli][debug] Arguments:
[cli][debug]  url=twitch.tv/esl_dota2
[cli][debug]  stream=['best']
[cli][debug]  --no-config=True
[cli][debug]  --loglevel=debug
[cli][debug]  --output=NUL
[cli][info] Found matching plugin twitch for URL twitch.tv/esl_dota2
[plugins.twitch][debug] Getting live HLS streams for esl_dota2
[plugins.twitch][debug] {'adblock': False, 'geoblock_reason': '', 'hide_ads': False, 'server_ads': True, 'show_ads': True}
[utils.l10n][debug] Language code: en_US
[cli][info] Available streams: audio_only, 160p (worst), 360p, 480p, 720p60, 1080p60 (best)
[cli][info] Opening stream: 1080p60 (hls)
[cli][info] Writing output to
\\.\NUL
[cli][debug] Checking file output
[stream.hls][debug] Reloading playlist
[cli][debug] Pre-buffering 8192 bytes
[stream.hls][debug] First Sequence: 16067; Last Sequence: 16081
[stream.hls][debug] Start offset: 0; Duration: None; Start Sequence: 16079; End Sequence: None
[stream.hls][debug] Adding segment 16079 to queue
[stream.hls][debug] Adding segment 16080 to queue
[stream.hls][debug] Adding segment 16081 to queue
[stream.hls][debug] Writing segment 16079 to output
[stream.hls][debug] Segment 16079 complete
[cli][debug] Writing stream to output
[stream.hls][debug] Writing segment 16080 to output
[stream.hls][debug] Segment 16080 complete
[stream.hls][debug] Writing segment 16081 to output
[stream.hls][debug] Segment 16081 complete
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Adding segment 16082 to queue
[stream.hls][debug] Adding segment 16083 to queue
[stream.hls][debug] Adding segment 16084 to queue
[stream.hls][debug] Writing segment 16082 to output
[stream.hls][debug] Segment 16082 complete
[stream.hls][debug] Writing segment 16083 to output
[stream.hls][debug] Segment 16083 complete
[stream.hls][debug] Writing segment 16084 to output
[stream.hls][debug] Segment 16084 complete
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Adding segment 16085 to queue
[stream.hls][debug] Adding segment 16086 to queue
[stream.hls][debug] Adding segment 16087 to queue
[stream.hls][debug] Writing segment 16085 to output
[stream.hls][debug] Segment 16085 complete
[stream.segmented][debug] Closing worker thread
[stream.segmented][debug] Closing writer thread
[stream.hls][debug] Writing segment 16086 to output
[stream.hls][debug] Segment 16086 complete
[cli][info] Stream ended
Interrupted! Exiting...
[cli][info] Closing currently open stream...
[download] Written 9.19 MiB to \\.\NUL (11s @ 843.14 KiB/s)

PowerShell 5

Uses classic Windows Console APIs

$ streamlink --no-config -l debug -o NUL twitch.tv/esl_dota2 best
[cli][debug] OS:         Windows 10
[cli][debug] Python:     3.13.0a6
[cli][debug] OpenSSL:    OpenSSL 3.0.13 30 Jan 2024
[cli][debug] Streamlink: 7.2.0+11.gfdef0703
[cli][debug] Dependencies:
[cli][debug]  certifi: 2024.2.2
[cli][debug]  exceptiongroup: 1.2.1
[cli][debug]  isodate: 0.6.1
[cli][debug]  lxml: 5.2.1
[cli][debug]  pycountry: 23.12.11
[cli][debug]  pycryptodome: 3.20.0
[cli][debug]  PySocks: 1.7.1
[cli][debug]  requests: 2.31.0
[cli][debug]  trio: 0.25.0
[cli][debug]  trio-websocket: 0.11.1
[cli][debug]  urllib3: 2.2.1
[cli][debug]  websocket-client: 1.8.0
[cli][debug] Arguments:
[cli][debug]  url=twitch.tv/esl_dota2
[cli][debug]  stream=['best']
[cli][debug]  --no-config=True
[cli][debug]  --loglevel=debug
[cli][debug]  --output=NUL
[cli][info] Found matching plugin twitch for URL twitch.tv/esl_dota2
[plugins.twitch][debug] Getting live HLS streams for esl_dota2
[plugins.twitch][debug] {'adblock': False, 'geoblock_reason': '', 'hide_ads': False, 'server_ads': True, 'show_ads': True}
[utils.l10n][debug] Language code: en_US
[cli][info] Available streams: audio_only, 160p (worst), 360p, 480p, 720p60, 1080p60 (best)
[cli][info] Opening stream: 1080p60 (hls)
[cli][info] Writing output to
\\.\NUL
[cli][debug] Checking file output
[stream.hls][debug] Reloading playlist
[cli][debug] Pre-buffering 8192 bytes
[stream.hls][debug] First Sequence: 16132; Last Sequence: 16146
[stream.hls][debug] Start offset: 0; Duration: None; Start Sequence: 16144; End Sequence: None
[stream.hls][debug] Adding segment 16144 to queue
[stream.hls][debug] Adding segment 16145 to queue
[stream.hls][debug] Adding segment 16146 to queue
[stream.hls][debug] Writing segment 16144 to output
[stream.hls][debug] Segment 16144 complete
[cli][debug] Writing stream to output
[stream.hls][debug] Writing segment 16145 to output
[stream.hls][debug] Segment 16145 complete
[stream.hls][debug] Writing segment 16146 to output
[stream.hls][debug] Segment 16146 complete
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Adding segment 16147 to queue
[stream.hls][debug] Adding segment 16148 to queue
[stream.hls][debug] Adding segment 16149 to queue
[stream.hls][debug] Writing segment 16147 to output
[stream.hls][debug] Segment 16147 complete
[stream.hls][debug] Writing segment 16148 to output
[stream.hls][debug] Segment 16148 complete
[stream.hls][debug] Writing segment 16149 to output
[stream.hls][debug] Segment 16149 complete
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Adding segment 16150 to queue
[stream.hls][debug] Adding segment 16151 to queue
[stream.hls][debug] Adding segment 16152 to queue
[stream.hls][debug] Writing segment 16150 to output
[stream.hls][debug] Segment 16150 complete
[stream.segmented][debug] Closing worker thread
[stream.segmented][debug] Closing writer thread
[stream.hls][debug] Writing segment 16151 to output
[stream.hls][debug] Segment 16151 complete
[cli][info] Stream ended
Interrupted! Exiting...
[cli][info] Closing currently open stream...
[download] Written 9.17 MiB to \\.\NUL (11s @ 836.38 KiB/s)

CMD

Uses classic Windows Console APIs

$ (streamlink-313) C:\Users\basti\Desktop\streamlink>streamlink --no-config -l debug -o NUL twitch.tv/esl_dota2 best
[cli][debug] OS:         Windows 10
[cli][debug] Python:     3.13.0a6
[cli][debug] OpenSSL:    OpenSSL 3.0.13 30 Jan 2024
[cli][debug] Streamlink: 7.2.0+11.gfdef0703
[cli][debug] Dependencies:
[cli][debug]  certifi: 2024.2.2
[cli][debug]  exceptiongroup: 1.2.1
[cli][debug]  isodate: 0.6.1
[cli][debug]  lxml: 5.2.1
[cli][debug]  pycountry: 23.12.11
[cli][debug]  pycryptodome: 3.20.0
[cli][debug]  PySocks: 1.7.1
[cli][debug]  requests: 2.31.0
[cli][debug]  trio: 0.25.0
[cli][debug]  trio-websocket: 0.11.1
[cli][debug]  urllib3: 2.2.1
[cli][debug]  websocket-client: 1.8.0
[cli][debug] Arguments:
[cli][debug]  url=twitch.tv/esl_dota2
[cli][debug]  stream=['best']
[cli][debug]  --no-config=True
[cli][debug]  --loglevel=debug
[cli][debug]  --output=NUL
[cli][info] Found matching plugin twitch for URL twitch.tv/esl_dota2
[plugins.twitch][debug] Getting live HLS streams for esl_dota2
[plugins.twitch][debug] {'adblock': False, 'geoblock_reason': '', 'hide_ads': False, 'server_ads': True, 'show_ads': True}
[utils.l10n][debug] Language code: en_US
[cli][info] Available streams: audio_only, 160p (worst), 360p, 480p, 720p60, 1080p60 (best)
[cli][info] Opening stream: 1080p60 (hls)
[cli][info] Writing output to
\\.\NUL
[cli][debug] Checking file output
[stream.hls][debug] Reloading playlist
[cli][debug] Pre-buffering 8192 bytes
[stream.hls][debug] First Sequence: 16208; Last Sequence: 16222
[stream.hls][debug] Start offset: 0; Duration: None; Start Sequence: 16220; End Sequence: None
[stream.hls][debug] Adding segment 16220 to queue
[stream.hls][debug] Adding segment 16221 to queue
[stream.hls][debug] Adding segment 16222 to queue
[stream.hls][debug] Writing segment 16220 to output
[stream.hls][debug] Segment 16220 complete
[cli][debug] Writing stream to output
[stream.hls][debug] Writing segment 16221 to output
[stream.hls][debug] Segment 16221 complete
[stream.hls][debug] Writing segment 16222 to output
[stream.hls][debug] Segment 16222 complete
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Adding segment 16223 to queue
[stream.hls][debug] Adding segment 16224 to queue
[stream.hls][debug] Adding segment 16225 to queue
[stream.hls][debug] Writing segment 16223 to output
[stream.hls][debug] Segment 16223 complete
[stream.hls][debug] Writing segment 16224 to output
[stream.hls][debug] Segment 16224 complete
[stream.hls][debug] Writing segment 16225 to output
[stream.hls][debug] Segment 16225 complete
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Adding segment 16226 to queue
[stream.hls][debug] Adding segment 16227 to queue
[stream.hls][debug] Adding segment 16228 to queue
[stream.hls][debug] Writing segment 16226 to output
[stream.hls][debug] Segment 16226 complete
[stream.segmented][debug] Closing worker thread
[stream.segmented][debug] Closing writer thread
[stream.hls][debug] Writing segment 16227 to output
[stream.hls][debug] Segment 16227 complete
[cli][info] Stream ended
Interrupted! Exiting...
[cli][info] Closing currently open stream...
[download] Written 9.18 MiB to \\.\NUL (11s @ 839.89 KiB/s)

BASH on MinTTY (MinGW64 / MSYS2)

Uses classic Windows Console APIs and requires the winpty wrapper, as explained above (stdio streams are wrapped and confuse the isatty() check)

$ winpty streamlink --no-config -l debug -o /dev/null twitch.tv/esl_dota2 best
[cli][debug] OS:         Windows 10
[cli][debug] Python:     3.13.0a6
[cli][debug] OpenSSL:    OpenSSL 3.0.13 30 Jan 2024
[cli][debug] Streamlink: 7.2.0+11.gfdef0703
[cli][debug] Dependencies:
[cli][debug]  certifi: 2024.2.2
[cli][debug]  exceptiongroup: 1.2.1
[cli][debug]  isodate: 0.6.1
[cli][debug]  lxml: 5.2.1
[cli][debug]  pycountry: 23.12.11
[cli][debug]  pycryptodome: 3.20.0
[cli][debug]  PySocks: 1.7.1
[cli][debug]  requests: 2.31.0
[cli][debug]  trio: 0.25.0
[cli][debug]  trio-websocket: 0.11.1
[cli][debug]  urllib3: 2.2.1
[cli][debug]  websocket-client: 1.8.0
[cli][debug] Arguments:
[cli][debug]  url=twitch.tv/esl_dota2
[cli][debug]  stream=['best']
[cli][debug]  --no-config=True
[cli][debug]  --loglevel=debug
[cli][debug]  --output=nul
[cli][info] Found matching plugin twitch for URL twitch.tv/esl_dota2
[plugins.twitch][debug] Getting live HLS streams for esl_dota2
[plugins.twitch][debug] {'adblock': False, 'geoblock_reason': '', 'hide_ads': False, 'server_ads': True, 'show_ads': True}
[utils.l10n][debug] Language code: en_US
[cli][info] Available streams: audio_only, 160p (worst), 360p, 480p, 720p60, 1080p60 (best)
[cli][info] Opening stream: 1080p60 (hls)
[cli][info] Writing output to
\\.\NUL
[cli][debug] Checking file output
[stream.hls][debug] Reloading playlist
[cli][debug] Pre-buffering 8192 bytes
[stream.hls][debug] First Sequence: 16275; Last Sequence: 16289
[stream.hls][debug] Start offset: 0; Duration: None; Start Sequence: 16287; End Sequence: None
[stream.hls][debug] Adding segment 16287 to queue
[stream.hls][debug] Adding segment 16288 to queue
[stream.hls][debug] Adding segment 16289 to queue
[stream.hls][debug] Writing segment 16287 to output
[stream.hls][debug] Segment 16287 complete
[cli][debug] Writing stream to output
[stream.hls][debug] Writing segment 16288 to output
[stream.hls][debug] Segment 16288 complete
[stream.hls][debug] Writing segment 16289 to output
[stream.hls][debug] Segment 16289 complete
[stream.hls][debug] Reloading playlist
[stream.hls][debug] Adding segment 16290 to queue
[stream.hls][debug] Adding segment 16291 to queue
[stream.hls][debug] Adding segment 16292 to queue
[stream.hls][debug] Writing segment 16290 to output
[stream.hls][debug] Segment 16290 complete
[stream.segmented][debug] Closing worker thread
[stream.segmented][debug] Closing writer thread
[stream.hls][debug] Writing segment 16291 to output
[stream.hls][debug] Segment 16291 complete
[cli][info] Stream ended
Interrupted! Exiting...
[cli][info] Closing currently open stream...
[download] Written 4.59 MiB to \\.\NUL (5s @ 904.64 KiB/s)

@bastimeyer bastimeyer merged commit aa0270f into streamlink:master Apr 12, 2025
23 checks passed
@bastimeyer bastimeyer deleted the cli/console/status branch April 12, 2025 11:54
@bubbaprog
Copy link

I'm not super clear on exactly what changed here and how but I can tell you that all my scripts that invoke streamlink no longer show any status or progress at all after upgrading to 7.3.0.

@bastimeyer
Copy link
Member Author

all my scripts that invoke streamlink

https://github.com/streamlink/streamlink/blob/7.3.0/CHANGELOG.md?plain=1#L6

--progress=force

@bubbaprog
Copy link

all my scripts that invoke streamlink

https://github.com/streamlink/streamlink/blob/7.3.0/CHANGELOG.md?plain=1#L6

--progress=force

I guess I'm asking why the default is now not to show progress/status...I don't even know where or how this change is supposed to be displaying progress/status; my terminal programs and environments are extremely commonplace (GNOME Terminal in Ubuntu!)

Almost every change I've tracked and occasionally contributed to with this project I've understood except this one, while it's trivial for me to add progress=force to my config file it just seems like an odd thing to add.

@bastimeyer
Copy link
Member Author

Post the actual issue/error and how you're running Streamlink instead of trying to describe what's wrong.

The progress output is working perfectly fine, even in Gnome Terminal on Ubuntu.

I guess I'm asking why the default is now not to show progress/status...

It is not. The default value is still yes. Whether progress output is shown however depends on the environment.

As you can read in the changelog and the description of this merged PR you're commenting on, the progress output has been moved from hardcoded stderr to Streamlink's ConsoleOutput, whose output stream defaults to stdout or falls back to stderr if --stdout is set. The ConsoleOutput however requires a TTY when writing status messages (the download progress), meaning an interactive output stream of your terminal (e.g. Gnome Terminal). Otherwise, no download progress messages will be written, unless --progress=force is set. In this case, less frequent non-status progress messages are written.

So if you're running Streamlink with the --output/--record argument and you're piping the console output stream (stdout by default) into a file or into the stdin stream of another program, then there will be no progress output unless --progress=force is set. Previously, Streamlink didn't care about any of this and simply wrote the status messages to stderr no matter what.

The motivation for these changes have been clearly pointed out in the linked issue this PR has closed, namely #6449.

@bubbaprog
Copy link

OK, I've figured out what's going on here and it's that the behavior of --quiet has changed, which yes was in the writeup but for which I didn't quite grasp what difference that would make.

Thanks for all your efforts here.

snorkelopstesting2-coder pushed a commit to snorkel-marlin-repos/streamlink_streamlink_pr_6497_5c787654-e129-4520-9917-0f335175d58c that referenced this pull request Oct 22, 2025
Original PR #6497 by bastimeyer
Original: streamlink/streamlink#6497
snorkelopstesting4-web added a commit to snorkel-marlin-repos/streamlink_streamlink_pr_6497_5c787654-e129-4520-9917-0f335175d58c that referenced this pull request Oct 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

cli: write download progress to the same stream as the logger/consoleoutput, keep sticky status line at the bottom, make other messages flow above

2 participants