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/__about__.py +27 -0
- eyed3/__init__.py +38 -0
- eyed3/__regarding__.py +48 -0
- eyed3/core.py +457 -0
- eyed3/id3/__init__.py +544 -0
- eyed3/id3/apple.py +58 -0
- eyed3/id3/frames.py +2261 -0
- eyed3/id3/headers.py +696 -0
- eyed3/id3/tag.py +2047 -0
- eyed3/main.py +305 -0
- eyed3/mimetype.py +107 -0
- eyed3/mp3/__init__.py +188 -0
- eyed3/mp3/headers.py +866 -0
- eyed3/plugins/__init__.py +200 -0
- eyed3/plugins/art.py +266 -0
- eyed3/plugins/classic.py +1173 -0
- eyed3/plugins/extract.py +61 -0
- eyed3/plugins/fixup.py +631 -0
- eyed3/plugins/genres.py +48 -0
- eyed3/plugins/itunes.py +64 -0
- eyed3/plugins/jsontag.py +133 -0
- eyed3/plugins/lameinfo.py +86 -0
- eyed3/plugins/lastfm.py +50 -0
- eyed3/plugins/mimetype.py +93 -0
- eyed3/plugins/nfo.py +123 -0
- eyed3/plugins/pymod.py +72 -0
- eyed3/plugins/stats.py +479 -0
- eyed3/plugins/xep_118.py +45 -0
- eyed3/plugins/yamltag.py +25 -0
- eyed3/utils/__init__.py +443 -0
- eyed3/utils/art.py +79 -0
- eyed3/utils/binfuncs.py +153 -0
- eyed3/utils/console.py +553 -0
- eyed3/utils/log.py +59 -0
- eyed3/utils/prompt.py +90 -0
- eyed3-0.9.8a1.dist-info/METADATA +163 -0
- eyed3-0.9.8a1.dist-info/RECORD +42 -0
- eyed3-0.9.8a1.dist-info/WHEEL +5 -0
- eyed3-0.9.8a1.dist-info/entry_points.txt +2 -0
- eyed3-0.9.8a1.dist-info/licenses/AUTHORS.rst +39 -0
- eyed3-0.9.8a1.dist-info/licenses/LICENSE +675 -0
- eyed3-0.9.8a1.dist-info/top_level.txt +1 -0
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
|