eyeD3 0.9.8a1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
eyed3/utils/console.py ADDED
@@ -0,0 +1,553 @@
1
+ import os
2
+ import struct
3
+ import sys
4
+ import time
5
+ import typing
6
+ from typing import Union
7
+
8
+ try:
9
+ import fcntl
10
+ import termios
11
+ import signal
12
+ _CAN_RESIZE_TERMINAL = True
13
+ except ImportError:
14
+ _CAN_RESIZE_TERMINAL = False
15
+
16
+ from . import formatSize, formatTime
17
+
18
+
19
+ class AnsiCodes(object):
20
+ _USE_ANSI = False
21
+ _CSI = '\033['
22
+
23
+ def __init__(self, codes):
24
+ def code_to_chars(code):
25
+ return AnsiCodes._CSI + str(code) + 'm'
26
+
27
+ for name in dir(codes):
28
+ if not name.startswith('_'):
29
+ value = getattr(codes, name)
30
+ setattr(self, name, code_to_chars(value))
31
+
32
+ # Add color function
33
+ for reset_name in ("RESET_%s" % name, "RESET"):
34
+ if hasattr(codes, reset_name):
35
+ reset_value = getattr(codes, reset_name)
36
+ setattr(self, "%s" % name.lower(),
37
+ AnsiCodes._mkfunc(code_to_chars(value),
38
+ code_to_chars(reset_value)))
39
+ break
40
+
41
+ @staticmethod
42
+ def _mkfunc(color, reset):
43
+ def _cwrap(text, *styles):
44
+ if not AnsiCodes._USE_ANSI:
45
+ return text
46
+
47
+ s = ''
48
+ for st in styles:
49
+ s += st
50
+ s += color + text + reset
51
+ if styles:
52
+ s += Style.RESET_ALL
53
+ return s
54
+ return _cwrap
55
+
56
+ def __getattribute__(self, name):
57
+ attr = super(AnsiCodes, self).__getattribute__(name)
58
+ if (hasattr(attr, "startswith") and
59
+ attr.startswith(AnsiCodes._CSI) and
60
+ not AnsiCodes._USE_ANSI):
61
+ return ""
62
+ else:
63
+ return attr
64
+
65
+ def __getitem__(self, name):
66
+ return getattr(self, name.upper())
67
+
68
+ @classmethod
69
+ def init(cls, allow_colors):
70
+ cls._USE_ANSI = allow_colors and cls._term_supports_color()
71
+
72
+ @staticmethod
73
+ def _term_supports_color():
74
+ if (os.environ.get("TERM") == "dumb" or
75
+ os.environ.get("OS") == "Windows_NT"):
76
+ return False
77
+ return hasattr(sys.stdout, "isatty") and sys.stdout.isatty()
78
+
79
+
80
+ class AnsiFore:
81
+ GREY = 30 # noqa
82
+ RED = 31 # noqa
83
+ GREEN = 32 # noqa
84
+ YELLOW = 33 # noqa
85
+ BLUE = 34 # noqa
86
+ MAGENTA = 35 # noqa
87
+ CYAN = 36 # noqa
88
+ WHITE = 37 # noqa
89
+ RESET = 39 # noqa
90
+
91
+
92
+ class AnsiBack:
93
+ GREY = 40 # noqa
94
+ RED = 41 # noqa
95
+ GREEN = 42 # noqa
96
+ YELLOW = 43 # noqa
97
+ BLUE = 44 # noqa
98
+ MAGENTA = 45 # noqa
99
+ CYAN = 46 # noqa
100
+ WHITE = 47 # noqa
101
+ RESET = 49 # noqa
102
+
103
+
104
+ class AnsiStyle:
105
+ RESET_ALL = 0 # noqa
106
+ BRIGHT = 1 # noqa
107
+ RESET_BRIGHT = 22 # noqa
108
+ DIM = 2 # noqa
109
+ RESET_DIM = RESET_BRIGHT # noqa
110
+ ITALICS = 3 # noqa
111
+ RESET_ITALICS = 23 # noqa
112
+ UNDERLINE = 4 # noqa
113
+ RESET_UNDERLINE = 24 # noqa
114
+ BLINK_SLOW = 5 # noqa
115
+ RESET_BLINK_SLOW = 25 # noqa
116
+ BLINK_FAST = 6 # noqa
117
+ RESET_BLINK_FAST = 26 # noqa
118
+ INVERSE = 7 # noqa
119
+ RESET_INVERSE = 27 # noqa
120
+ STRIKE_THRU = 9 # noqa
121
+ RESET_STRIKE_THRU = 29 # noqa
122
+
123
+
124
+ Fore = AnsiCodes(AnsiFore)
125
+ Back = AnsiCodes(AnsiBack)
126
+ Style = AnsiCodes(AnsiStyle)
127
+
128
+
129
+ def ERROR_COLOR():
130
+ return Fore.RED
131
+
132
+
133
+ def WARNING_COLOR():
134
+ return Fore.YELLOW
135
+
136
+
137
+ def HEADER_COLOR():
138
+ return Fore.GREEN
139
+
140
+
141
+ class Spinner(object):
142
+ """
143
+ A class to display a spinner in the terminal.
144
+
145
+ It is designed to be used with the `with` statement::
146
+
147
+ with Spinner("Reticulating splines", "green") as s:
148
+ for item in enumerate(items):
149
+ s.next()
150
+ """
151
+ _default_unicode_chars = "◓◑◒◐"
152
+ _default_ascii_chars = "-/|\\"
153
+
154
+ def __init__(self, msg, file=None, step=1,
155
+ chars=None, use_unicode=True, print_done=True):
156
+
157
+ self._msg = msg
158
+ self._file = file or sys.stdout
159
+ self._step = step
160
+ if not chars:
161
+ if use_unicode:
162
+ chars = self._default_unicode_chars
163
+ else:
164
+ chars = self._default_ascii_chars
165
+ self._chars = chars
166
+
167
+ self._silent = not self._file.isatty()
168
+ self._print_done = print_done
169
+
170
+ def _iterator(self):
171
+ chars = self._chars
172
+ index = 0
173
+ write = self._file.write
174
+ flush = self._file.flush
175
+
176
+ while True:
177
+ write("\r")
178
+ write(self._msg)
179
+ write(" ")
180
+ write(chars[index])
181
+ flush()
182
+ yield
183
+
184
+ for _ in range(self._step):
185
+ yield
186
+
187
+ index += 1
188
+ if index == len(chars):
189
+ index = 0
190
+
191
+ def __enter__(self):
192
+ if self._silent:
193
+ return self._silent_iterator()
194
+ else:
195
+ return self._iterator()
196
+
197
+ def __exit__(self, exc_type, exc_value, traceback):
198
+ write = self._file.write
199
+ flush = self._file.flush
200
+
201
+ if not self._silent:
202
+ write("\r")
203
+ write(self._msg)
204
+ if self._print_done:
205
+ if exc_type is None:
206
+ write(Fore.GREEN + ' [Done]\n')
207
+ else:
208
+ write(Fore.RED + ' [Failed]\n')
209
+ else:
210
+ write("\n")
211
+ flush()
212
+
213
+ def _silent_iterator(self):
214
+ self._file.write(self._msg)
215
+ self._file.flush()
216
+
217
+ while True:
218
+ yield
219
+
220
+
221
+ class ProgressBar(object):
222
+ """
223
+ A class to display a progress bar in the terminal.
224
+
225
+ It is designed to be used either with the `with` statement::
226
+
227
+ with ProgressBar(len(items)) as bar:
228
+ for item in enumerate(items):
229
+ bar.update()
230
+
231
+ or as a generator::
232
+
233
+ for item in ProgressBar(items):
234
+ item.process()
235
+ """
236
+ def __init__(self, total_or_items: Union[int, typing.Sequence], file=None):
237
+ """
238
+ total_or_items : int or sequence
239
+ If an int, the number of increments in the process being
240
+ tracked. If a sequence, the items to iterate over.
241
+
242
+ file : writable file-like object, optional
243
+ The file to write the progress bar to. Defaults to
244
+ `sys.stdout`. If `file` is not a tty (as determined by
245
+ calling its `isatty` member, if any), the scrollbar will
246
+ be completely silent.
247
+ """
248
+ self._file = file or sys.stdout
249
+
250
+ if not self._file.isatty():
251
+ self.update = self._silent_update
252
+ self._silent = True
253
+ else:
254
+ self._silent = False
255
+
256
+ try:
257
+ self._items = iter(total_or_items)
258
+ self._total = len(total_or_items)
259
+ except TypeError:
260
+ try:
261
+ self._total = int(total_or_items)
262
+ self._items = iter(range(self._total))
263
+ except TypeError:
264
+ raise TypeError("First argument must be int or sequence")
265
+
266
+ self._start_time = time.time()
267
+
268
+ self._should_handle_resize = (
269
+ _CAN_RESIZE_TERMINAL and self._file.isatty())
270
+ self._handle_resize()
271
+ if self._should_handle_resize:
272
+ signal.signal(signal.SIGWINCH, self._handle_resize)
273
+ self._signal_set = True
274
+ else:
275
+ self._signal_set = False
276
+
277
+ self.update(0)
278
+
279
+ def _handle_resize(self, signum=None, frame=None):
280
+ self._terminal_width = getTtySize(self._file,
281
+ self._should_handle_resize)[1]
282
+
283
+ def __enter__(self):
284
+ return self
285
+
286
+ def __exit__(self, exc_type, exc_value, traceback):
287
+ if not self._silent:
288
+ if exc_type is None:
289
+ self.update(self._total)
290
+ self._file.write('\n')
291
+ self._file.flush()
292
+ if self._signal_set:
293
+ signal.signal(signal.SIGWINCH, signal.SIG_DFL)
294
+
295
+ def __iter__(self):
296
+ return self
297
+
298
+ def __next__(self):
299
+ try:
300
+ rv = next(self._items)
301
+ except StopIteration:
302
+ self.__exit__(None, None, None)
303
+ raise
304
+ else:
305
+ self.update()
306
+ return rv
307
+
308
+ def update(self, value=None):
309
+ """
310
+ Update the progress bar to the given value (out of the total
311
+ given to the constructor).
312
+ """
313
+ if value is None:
314
+ value = self._current_value = self._current_value + 1
315
+ else:
316
+ self._current_value = value
317
+ if self._total == 0:
318
+ frac = 1.0
319
+ else:
320
+ frac = float(value) / float(self._total)
321
+
322
+ file = self._file
323
+ write = file.write
324
+
325
+ suffix = self._formatSuffix(value, frac)
326
+ self._bar_length = self._terminal_width - 37
327
+
328
+ bar_fill = int(float(self._bar_length) * frac)
329
+ write("\r|")
330
+ write(Fore.BLUE + '=' * bar_fill + Fore.RESET)
331
+ if bar_fill < self._bar_length:
332
+ write(Fore.GREEN + '>' + Fore.RESET)
333
+ write("-" * (self._bar_length - bar_fill - 1))
334
+ write("|")
335
+ write(suffix)
336
+
337
+ self._file.flush()
338
+
339
+ def _formatSuffix(self, value, frac):
340
+
341
+ if value >= self._total:
342
+ t = time.time() - self._start_time
343
+ time_str = ' '
344
+ elif value <= 0:
345
+ t = None
346
+ time_str = ''
347
+ else:
348
+ t = ((time.time() - self._start_time) * (1.0 - frac)) / frac
349
+ time_str = ' ETA '
350
+ if t is not None:
351
+ time_str += formatTime(t, short=True)
352
+
353
+ suffix = ' {0:>4s}/{1:>4s}'.format(formatSize(value, short=True),
354
+ formatSize(self._total, short=True))
355
+ suffix += ' ({0:>6s}%)'.format("{0:.2f}".format(frac * 100.0))
356
+ suffix += time_str
357
+
358
+ return suffix
359
+
360
+ def _silent_update(self, value=None):
361
+ pass
362
+
363
+ @classmethod
364
+ def map(cls, function, items, multiprocess=False, file=None):
365
+ """
366
+ Does a `map` operation while displaying a progress bar with
367
+ percentage complete.
368
+
369
+ ::
370
+
371
+ def work(i):
372
+ print(i)
373
+
374
+ ProgressBar.map(work, range(50))
375
+
376
+ Parameters:
377
+
378
+ function : function
379
+ Function to call for each step
380
+
381
+ items : sequence
382
+ Sequence where each element is a tuple of arguments to pass to
383
+ *function*.
384
+
385
+ multiprocess : bool, optional
386
+ If `True`, use the `multiprocessing` module to distribute each
387
+ task to a different processor core.
388
+
389
+ file : writeable file-like object, optional
390
+ The file to write the progress bar to. Defaults to
391
+ `sys.stdout`. If `file` is not a tty (as determined by
392
+ calling its `isatty` member, if any), the scrollbar will
393
+ be completely silent.
394
+ """
395
+ results = []
396
+
397
+ if file is None:
398
+ file = sys.stdout
399
+
400
+ with cls(len(items), file=file) as bar:
401
+ step_size = max(200, bar._bar_length)
402
+ steps = max(int(float(len(items)) / step_size), 1)
403
+ if not multiprocess:
404
+ for i, item in enumerate(items):
405
+ function(item)
406
+ if (i % steps) == 0:
407
+ bar.update(i)
408
+ else:
409
+ import multiprocessing
410
+ p = multiprocessing.Pool()
411
+ for i, result in enumerate(p.imap_unordered(function, items,
412
+ steps)):
413
+ bar.update(i)
414
+ results.append(result)
415
+
416
+ return results
417
+
418
+
419
+ def printMsg(s):
420
+ fp = sys.stdout
421
+ assert isinstance(s, str)
422
+ try:
423
+ fp.write("%s\n" % s)
424
+ except UnicodeEncodeError:
425
+ fp.write("%s\n" % str(s.encode("utf-8", "replace"), "utf-8"))
426
+ fp.flush()
427
+
428
+
429
+ def printError(s):
430
+ _printWithColor(s, ERROR_COLOR(), sys.stderr)
431
+
432
+
433
+ def printWarning(s):
434
+ _printWithColor(s, WARNING_COLOR(), sys.stdout)
435
+
436
+
437
+ def printHeader(s):
438
+ _printWithColor(s, HEADER_COLOR(), sys.stdout)
439
+
440
+
441
+ def boldText(s, c=None):
442
+ return formatText(s, b=True, c=c)
443
+
444
+
445
+ def formatText(s, b=False, c=None):
446
+ return ((Style.BRIGHT if b else '') +
447
+ (c or '') +
448
+ s +
449
+ (Fore.RESET if c else '') +
450
+ (Style.RESET_BRIGHT if b else ''))
451
+
452
+
453
+ def _printWithColor(s, color, file):
454
+ assert isinstance(s, str)
455
+ file.write(color + s + Fore.RESET + '\n')
456
+ file.flush()
457
+
458
+
459
+ def cformat(msg, fg, bg=None, styles=None):
460
+ """Format ``msg`` with foreground and optional background. Optional
461
+ ``styles`` lists will also be applied. The formatted string is returned."""
462
+ fg = fg or ""
463
+ bg = bg or ""
464
+ styles = "".join(styles or [])
465
+ reset = Fore.RESET + Back.RESET + Style.RESET_ALL if (fg or bg or styles) \
466
+ else ""
467
+
468
+ output = "%(fg)s%(bg)s%(styles)s%(msg)s%(reset)s" % locals()
469
+ return output
470
+
471
+
472
+ def getTtySize(fd=sys.stdout, check_tty=True):
473
+ hw = None
474
+ if check_tty:
475
+ try:
476
+ data = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 4)
477
+ hw = struct.unpack("hh", data)
478
+ except (OSError, NameError):
479
+ pass
480
+ if not hw:
481
+ try:
482
+ hw = (int(os.environ.get('LINES')),
483
+ int(os.environ.get('COLUMNS')))
484
+ except (TypeError, ValueError):
485
+ hw = (78, 25)
486
+ return hw
487
+
488
+
489
+ def cprint(msg, fg, bg=None, styles=None, file=sys.stdout):
490
+ """Calls ``cformat`` and prints the result to output stream ``file``."""
491
+ print(cformat(msg, fg, bg=bg, styles=styles), file=file)
492
+
493
+
494
+ if __name__ == "__main__":
495
+ AnsiCodes.init(True)
496
+
497
+ def checkCode(c):
498
+ return (c[0] != '_' and
499
+ "RESET" not in c and
500
+ c[0] == c[0].upper()
501
+ )
502
+
503
+ for bg_name, bg_code in ((c, getattr(Back, c))
504
+ for c in dir(Back) if checkCode(c)):
505
+ sys.stdout.write('%s%-7s%s %s ' %
506
+ (bg_code, bg_name, Back.RESET, bg_code))
507
+ for _, fg_code in ((c, getattr(Fore, c))
508
+ for c in dir(Fore) if checkCode(c)):
509
+ sys.stdout.write(fg_code)
510
+ for st_name, st_code in ((c, getattr(Style, c))
511
+ for c in dir(Style) if checkCode(c)):
512
+ sys.stdout.write('%s%s %s %s' %
513
+ (st_code, st_name,
514
+ getattr(Style, "RESET_%s" % st_name),
515
+ bg_code))
516
+ sys.stdout.write("%s\n" % Style.RESET_ALL)
517
+
518
+ sys.stdout.write("\n")
519
+
520
+ with Spinner(Fore.GREEN + "Phase #1") as spinner:
521
+ for _ in range(50):
522
+ time.sleep(.05)
523
+ next(spinner)
524
+ with Spinner(Fore.RED + "Phase #2" + Fore.RESET,
525
+ print_done=False) as spinner:
526
+ for _ in range(50):
527
+ time.sleep(.05)
528
+ next(spinner)
529
+ with Spinner("Phase #3", print_done=False, use_unicode=False) as spinner:
530
+ for _ in range(50):
531
+ next(spinner)
532
+ time.sleep(.05)
533
+ with Spinner("Phase #4", print_done=False, chars='.oO°Oo.') as spinner:
534
+ for _ in range(50):
535
+ next(spinner)
536
+ time.sleep(.05)
537
+
538
+ items = [x for x in range(200)]
539
+ with ProgressBar(len(items)) as bar:
540
+ for _ in enumerate(items):
541
+ bar.update()
542
+ time.sleep(.05)
543
+
544
+ for _ in iter(ProgressBar(items)):
545
+ time.sleep(.05)
546
+
547
+ progress = 0
548
+ max_val = 320000000
549
+ with ProgressBar(max_val) as bar:
550
+ while progress < max_val:
551
+ progress += 23400
552
+ bar.update(progress)
553
+ time.sleep(.001)
eyed3/utils/log.py ADDED
@@ -0,0 +1,59 @@
1
+ import logging
2
+ from ..__about__ import __version__ as VERSION
3
+
4
+ DEFAULT_FORMAT = '%(name)s:%(levelname)s: %(message)s'
5
+ MAIN_LOGGER = "eyed3"
6
+
7
+ # Add some levels
8
+ logging.VERBOSE = logging.DEBUG + 1
9
+ logging.addLevelName(logging.VERBOSE, "VERBOSE")
10
+
11
+
12
+ class Logger(logging.Logger):
13
+ """Base class for all loggers"""
14
+
15
+ def __init__(self, name):
16
+ logging.Logger.__init__(self, name)
17
+
18
+ # Using propagation of child to parent, by default
19
+ self.propagate = True
20
+ self.setLevel(logging.NOTSET)
21
+
22
+ def verbose(self, msg, *args, **kwargs):
23
+ """Log \a msg at 'verbose' level, debug < verbose < info"""
24
+ self.log(logging.VERBOSE, msg, *args, **kwargs)
25
+
26
+
27
+ def getLogger(name):
28
+ og_class = logging.getLoggerClass()
29
+ try:
30
+ logging.setLoggerClass(Logger)
31
+ return logging.getLogger(name)
32
+ finally:
33
+ logging.setLoggerClass(og_class)
34
+
35
+
36
+ # The main 'eyed3' logger
37
+ log = getLogger(MAIN_LOGGER)
38
+ log.debug("eyeD3 version " + VERSION)
39
+ del VERSION
40
+
41
+
42
+ def initLogging():
43
+ """initialize the default logger with console output"""
44
+ logging.basicConfig()
45
+
46
+ # Don't propagate base 'eyed3'
47
+ log.propagate = False
48
+
49
+ console_handler = logging.StreamHandler()
50
+ console_handler.setFormatter(logging.Formatter(DEFAULT_FORMAT))
51
+ log.addHandler(console_handler)
52
+
53
+ log.setLevel(logging.ERROR)
54
+
55
+ return log
56
+
57
+
58
+ LEVELS = (logging.DEBUG, logging.VERBOSE, logging.INFO,
59
+ logging.WARNING, logging.ERROR, logging.CRITICAL)
eyed3/utils/prompt.py ADDED
@@ -0,0 +1,90 @@
1
+ import sys as _sys
2
+ from .console import Fore as fg
3
+
4
+ DISABLE_PROMPT = None
5
+ """Whenever a prompt occurs and this value is not ``None`` it can be ``exit``
6
+ to call sys.exit (see EXIT_STATUS) or ``raise`` to throw a RuntimeError,
7
+ which can be caught if desired."""
8
+
9
+ EXIT_STATUS = 2
10
+
11
+ BOOL_TRUE_RESPONSES = ("yes", "y", "true")
12
+
13
+
14
+ class PromptExit(RuntimeError):
15
+ """Raised when ``DISABLE_PROMPT`` is 'raise' and ``prompt`` is called."""
16
+ pass
17
+
18
+
19
+ def parseIntList(resp):
20
+ ints = set()
21
+ resp = resp.replace(',', ' ')
22
+ for c in resp.split():
23
+ i = int(c)
24
+ ints.add(i)
25
+ return list(ints)
26
+
27
+
28
+ def prompt(msg, default=None, required=True, type_=str,
29
+ validate=None, choices=None):
30
+ """Prompt user for input, the prequest is in ``msg``. If ``default`` is
31
+ not ``None`` it will be displayed as the default and returned if not
32
+ input is entered. The value ``None`` is only returned if ``required`` is
33
+ ``False``. The response is passed to ``type_`` for conversion (default
34
+ is unicode) before being returned. An optional list of valid responses can
35
+ be provided in ``choices``."""
36
+ yes_no_prompt = default is True or default is False
37
+
38
+ if yes_no_prompt:
39
+ default_str = "Yn" if default is True else "yN"
40
+ else:
41
+ default_str = str(default) if default else None
42
+
43
+ if default is not None:
44
+ msg = "%s [%s]" % (msg, default_str)
45
+ msg += ": " if not yes_no_prompt else "? "
46
+
47
+ if DISABLE_PROMPT:
48
+ if DISABLE_PROMPT == "exit":
49
+ print(msg + "\nPrompting is disabled, exiting.")
50
+ _sys.exit(EXIT_STATUS)
51
+ else:
52
+ raise PromptExit(msg)
53
+
54
+ resp = None
55
+ while resp is None:
56
+
57
+ try:
58
+ resp = input(msg)
59
+ except EOFError:
60
+ # Converting this allows main functions to catch without
61
+ # catching other eofs
62
+ raise PromptExit()
63
+
64
+ if not resp and default not in (None, ""):
65
+ resp = str(default)
66
+
67
+ if resp:
68
+ if yes_no_prompt:
69
+ resp = True if resp.lower() in BOOL_TRUE_RESPONSES else False
70
+ else:
71
+ resp = resp.strip()
72
+ try:
73
+ resp = type_(resp)
74
+ except Exception as ex:
75
+ print(fg.red(str(ex)))
76
+ resp = None
77
+ elif not required:
78
+ return None
79
+ else:
80
+ resp = None
81
+
82
+ if ((choices and resp not in choices) or
83
+ (validate and not validate(resp))):
84
+ if choices:
85
+ print(fg.red("Invalid response, choose from: ") + str(choices))
86
+ else:
87
+ print(fg.red("Invalid response"))
88
+ resp = None
89
+
90
+ return resp