]> jfr.im git - yt-dlp.git/blame - yt_dlp/minicurses.py
Improved progress reporting (See desc) (#1125)
[yt-dlp.git] / yt_dlp / minicurses.py
CommitLineData
bd50a52b 1from threading import Lock
819e0531 2from .utils import supports_terminal_sequences, TERMINAL_SEQUENCES
3
bd50a52b 4
819e0531 5class MultilinePrinterBase:
6 def __init__(self, stream=None, lines=1):
7 self.stream = stream
8 self.maximum = lines - 1
bd50a52b 9
bd50a52b
THD
10 def __enter__(self):
11 return self
12
13 def __exit__(self, *args):
14 self.end()
15
16 def print_at_line(self, text, pos):
17 pass
18
19 def end(self):
20 pass
21
819e0531 22 def _add_line_number(self, text, line):
23 if self.maximum:
24 return f'{line + 1}: {text}'
25 return text
bd50a52b 26
bd50a52b 27
819e0531 28class QuietMultilinePrinter(MultilinePrinterBase):
29 pass
bd50a52b 30
bd50a52b 31
819e0531 32class MultilineLogger(MultilinePrinterBase):
33 def print_at_line(self, text, pos):
34 # stream is the logger object, not an actual stream
35 self.stream.debug(self._add_line_number(text, pos))
bd50a52b 36
bd50a52b 37
819e0531 38class BreaklineStatusPrinter(MultilinePrinterBase):
39 def print_at_line(self, text, pos):
40 self.stream.write(self._add_line_number(text, pos) + '\n')
bd50a52b 41
819e0531 42
43class MultilinePrinter(MultilinePrinterBase):
44 def __init__(self, stream=None, lines=1, preserve_output=True):
45 super().__init__(stream, lines)
46 self.preserve_output = preserve_output
47 self._lastline = self._lastlength = 0
48 self._movelock = Lock()
49 self._HAVE_FULLCAP = supports_terminal_sequences(self.stream)
50
51 def lock(func):
52 def wrapper(self, *args, **kwargs):
53 with self._movelock:
54 return func(self, *args, **kwargs)
55 return wrapper
bd50a52b
THD
56
57 def _move_cursor(self, dest):
819e0531 58 current = min(self._lastline, self.maximum)
59 self.stream.write('\r')
60 distance = dest - current
61 if distance < 0:
62 self.stream.write(TERMINAL_SEQUENCES['UP'] * -distance)
63 elif distance > 0:
64 self.stream.write(TERMINAL_SEQUENCES['DOWN'] * distance)
65 self._lastline = dest
66
67 @lock
68 def print_at_line(self, text, pos):
69 if self._HAVE_FULLCAP:
70 self._move_cursor(pos)
71 self.stream.write(TERMINAL_SEQUENCES['ERASE_LINE'])
72 self.stream.write(text)
bd50a52b 73 return
bd50a52b 74
819e0531 75 text = self._add_line_number(text, pos)
76 textlen = len(text)
77 if self._lastline == pos:
78 # move cursor at the start of progress when writing to same line
79 self.stream.write('\r')
80 if self._lastlength > textlen:
81 text += ' ' * (self._lastlength - textlen)
82 self._lastlength = textlen
83 else:
84 # otherwise, break the line
85 self.stream.write('\n')
86 self._lastlength = textlen
87 self.stream.write(text)
88 self._lastline = pos
bd50a52b 89
819e0531 90 @lock
bd50a52b 91 def end(self):
819e0531 92 # move cursor to the end of the last line, and write line break
93 # so that other to_screen calls can precede
94 if self._HAVE_FULLCAP:
bd50a52b 95 self._move_cursor(self.maximum)
819e0531 96 if self.preserve_output:
bd50a52b 97 self.stream.write('\n')
819e0531 98 return
bd50a52b 99
819e0531 100 if self._HAVE_FULLCAP:
101 self.stream.write(
102 TERMINAL_SEQUENCES['ERASE_LINE']
103 + f'{TERMINAL_SEQUENCES["UP"]}{TERMINAL_SEQUENCES["ERASE_LINE"]}' * self.maximum)
104 else:
105 self.stream.write(' ' * self._lastlength)