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