dgenerate-ultralytics-headless 8.3.185__py3-none-any.whl → 8.3.187__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.
- {dgenerate_ultralytics_headless-8.3.185.dist-info → dgenerate_ultralytics_headless-8.3.187.dist-info}/METADATA +6 -8
- {dgenerate_ultralytics_headless-8.3.185.dist-info → dgenerate_ultralytics_headless-8.3.187.dist-info}/RECORD +31 -30
- tests/test_python.py +2 -10
- ultralytics/__init__.py +1 -1
- ultralytics/cfg/datasets/Argoverse.yaml +2 -2
- ultralytics/cfg/datasets/Objects365.yaml +3 -3
- ultralytics/cfg/datasets/SKU-110K.yaml +4 -4
- ultralytics/cfg/datasets/VOC.yaml +2 -4
- ultralytics/cfg/datasets/VisDrone.yaml +2 -2
- ultralytics/cfg/datasets/xView.yaml +2 -2
- ultralytics/data/build.py +2 -2
- ultralytics/data/utils.py +0 -2
- ultralytics/engine/exporter.py +4 -1
- ultralytics/engine/results.py +1 -4
- ultralytics/engine/trainer.py +3 -3
- ultralytics/models/sam/__init__.py +8 -2
- ultralytics/models/sam/modules/sam.py +6 -6
- ultralytics/models/sam/predict.py +363 -6
- ultralytics/solutions/region_counter.py +3 -2
- ultralytics/utils/__init__.py +25 -162
- ultralytics/utils/autodevice.py +1 -1
- ultralytics/utils/benchmarks.py +9 -8
- ultralytics/utils/callbacks/wb.py +9 -3
- ultralytics/utils/downloads.py +29 -19
- ultralytics/utils/logger.py +10 -11
- ultralytics/utils/plotting.py +13 -20
- ultralytics/utils/tqdm.py +462 -0
- {dgenerate_ultralytics_headless-8.3.185.dist-info → dgenerate_ultralytics_headless-8.3.187.dist-info}/WHEEL +0 -0
- {dgenerate_ultralytics_headless-8.3.185.dist-info → dgenerate_ultralytics_headless-8.3.187.dist-info}/entry_points.txt +0 -0
- {dgenerate_ultralytics_headless-8.3.185.dist-info → dgenerate_ultralytics_headless-8.3.187.dist-info}/licenses/LICENSE +0 -0
- {dgenerate_ultralytics_headless-8.3.185.dist-info → dgenerate_ultralytics_headless-8.3.187.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,462 @@
|
|
1
|
+
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
|
2
|
+
|
3
|
+
from __future__ import annotations
|
4
|
+
|
5
|
+
import os
|
6
|
+
import sys
|
7
|
+
import time
|
8
|
+
from functools import lru_cache
|
9
|
+
from typing import IO, Any
|
10
|
+
|
11
|
+
|
12
|
+
@lru_cache(maxsize=1)
|
13
|
+
def is_noninteractive_console():
|
14
|
+
"""Check for known non-interactive console environments."""
|
15
|
+
return "GITHUB_ACTIONS" in os.environ or "RUNPOD_POD_ID" in os.environ
|
16
|
+
|
17
|
+
|
18
|
+
class TQDM:
|
19
|
+
"""
|
20
|
+
Lightweight zero-dependency progress bar for Ultralytics.
|
21
|
+
|
22
|
+
Provides clean, rich-style progress bars suitable for various environments including Weights & Biases,
|
23
|
+
console outputs, and other logging systems. Features zero external dependencies, clean single-line output,
|
24
|
+
rich-style progress bars with Unicode block characters, context manager support, iterator protocol support,
|
25
|
+
and dynamic description updates.
|
26
|
+
|
27
|
+
Attributes:
|
28
|
+
iterable (object): Iterable to wrap with progress bar.
|
29
|
+
desc (str): Prefix description for the progress bar.
|
30
|
+
total (int): Expected number of iterations.
|
31
|
+
disable (bool): Whether to disable the progress bar.
|
32
|
+
unit (str): String for units of iteration.
|
33
|
+
unit_scale (bool): Auto-scale units flag.
|
34
|
+
unit_divisor (int): Divisor for unit scaling.
|
35
|
+
leave (bool): Whether to leave the progress bar after completion.
|
36
|
+
mininterval (float): Minimum time interval between updates.
|
37
|
+
initial (int): Initial counter value.
|
38
|
+
n (int): Current iteration count.
|
39
|
+
closed (bool): Whether the progress bar is closed.
|
40
|
+
bar_format (str): Custom bar format string.
|
41
|
+
file (object): Output file stream.
|
42
|
+
|
43
|
+
Methods:
|
44
|
+
update: Update progress by n steps.
|
45
|
+
set_description: Set or update the description.
|
46
|
+
set_postfix: Set postfix for the progress bar.
|
47
|
+
close: Close the progress bar and clean up.
|
48
|
+
refresh: Refresh the progress bar display.
|
49
|
+
clear: Clear the progress bar from display.
|
50
|
+
write: Write a message without breaking the progress bar.
|
51
|
+
|
52
|
+
Examples:
|
53
|
+
Basic usage with iterator:
|
54
|
+
>>> for i in TQDM(range(100)):
|
55
|
+
... time.sleep(0.01)
|
56
|
+
|
57
|
+
With custom description:
|
58
|
+
>>> pbar = TQDM(range(100), desc="Processing")
|
59
|
+
>>> for i in pbar:
|
60
|
+
... pbar.set_description(f"Processing item {i}")
|
61
|
+
|
62
|
+
Context manager usage:
|
63
|
+
>>> with TQDM(total=100, unit="B", unit_scale=True) as pbar:
|
64
|
+
... for i in range(100):
|
65
|
+
... pbar.update(1)
|
66
|
+
|
67
|
+
Manual updates:
|
68
|
+
>>> pbar = TQDM(total=100, desc="Training")
|
69
|
+
>>> for epoch in range(100):
|
70
|
+
... # Do work
|
71
|
+
... pbar.update(1)
|
72
|
+
>>> pbar.close()
|
73
|
+
"""
|
74
|
+
|
75
|
+
# Constants
|
76
|
+
MIN_RATE_CALC_INTERVAL = 0.01 # Minimum time interval for rate calculation
|
77
|
+
RATE_SMOOTHING_FACTOR = 0.3 # Factor for exponential smoothing of rates
|
78
|
+
MAX_SMOOTHED_RATE = 1000000 # Maximum rate to apply smoothing to
|
79
|
+
NONINTERACTIVE_MIN_INTERVAL = 60.0 # Minimum interval for non-interactive environments
|
80
|
+
|
81
|
+
def __init__(
|
82
|
+
self,
|
83
|
+
iterable: Any = None,
|
84
|
+
desc: str | None = None,
|
85
|
+
total: int | None = None,
|
86
|
+
leave: bool = True,
|
87
|
+
file: IO[str] | None = None,
|
88
|
+
mininterval: float = 0.1,
|
89
|
+
disable: bool | None = None,
|
90
|
+
unit: str = "it",
|
91
|
+
unit_scale: bool = False,
|
92
|
+
unit_divisor: int = 1000,
|
93
|
+
bar_format: str | None = None,
|
94
|
+
initial: int = 0,
|
95
|
+
**kwargs, # Accept unused args for compatibility
|
96
|
+
) -> None:
|
97
|
+
"""
|
98
|
+
Initialize the TQDM progress bar with specified configuration options.
|
99
|
+
|
100
|
+
Args:
|
101
|
+
iterable (object, optional): Iterable to wrap with progress bar.
|
102
|
+
desc (str, optional): Prefix description for the progress bar.
|
103
|
+
total (int, optional): Expected number of iterations.
|
104
|
+
leave (bool, optional): Whether to leave the progress bar after completion.
|
105
|
+
file (object, optional): Output file stream for progress display.
|
106
|
+
mininterval (float, optional): Minimum time interval between updates (default 0.1s, 60s in GitHub Actions).
|
107
|
+
disable (bool, optional): Whether to disable the progress bar. Auto-detected if None.
|
108
|
+
unit (str, optional): String for units of iteration (default "it" for items).
|
109
|
+
unit_scale (bool, optional): Auto-scale units for bytes/data units.
|
110
|
+
unit_divisor (int, optional): Divisor for unit scaling (default 1000).
|
111
|
+
bar_format (str, optional): Custom bar format string.
|
112
|
+
initial (int, optional): Initial counter value.
|
113
|
+
**kwargs (Any): Additional keyword arguments for compatibility (ignored).
|
114
|
+
|
115
|
+
Examples:
|
116
|
+
>>> pbar = TQDM(range(100), desc="Processing")
|
117
|
+
>>> with TQDM(total=1000, unit="B", unit_scale=True) as pbar:
|
118
|
+
... pbar.update(1024) # Updates by 1KB
|
119
|
+
"""
|
120
|
+
# Disable if not verbose
|
121
|
+
if disable is None:
|
122
|
+
try:
|
123
|
+
from ultralytics.utils import LOGGER, VERBOSE
|
124
|
+
|
125
|
+
disable = not VERBOSE or LOGGER.getEffectiveLevel() > 20
|
126
|
+
except ImportError:
|
127
|
+
disable = False
|
128
|
+
|
129
|
+
self.iterable = iterable
|
130
|
+
self.desc = desc or ""
|
131
|
+
self.total = total if total is not None else (len(iterable) if hasattr(iterable, "__len__") else None)
|
132
|
+
self.disable = disable
|
133
|
+
self.unit = unit
|
134
|
+
self.unit_scale = unit_scale
|
135
|
+
self.unit_divisor = unit_divisor
|
136
|
+
self.leave = leave
|
137
|
+
self.noninteractive = is_noninteractive_console()
|
138
|
+
self.mininterval = max(mininterval, self.NONINTERACTIVE_MIN_INTERVAL) if self.noninteractive else mininterval
|
139
|
+
self.initial = initial
|
140
|
+
|
141
|
+
# Set bar format based on whether we have a total
|
142
|
+
if self.total is not None:
|
143
|
+
self.bar_format = bar_format or "{desc}: {percentage:3.0f}% {bar} {n_fmt}/{total_fmt} {rate_fmt} {elapsed}"
|
144
|
+
else:
|
145
|
+
self.bar_format = bar_format or "{desc}: {bar} {n_fmt} {rate_fmt} {elapsed}"
|
146
|
+
|
147
|
+
self.file = file or sys.stdout
|
148
|
+
|
149
|
+
# Internal state
|
150
|
+
self.n = self.initial
|
151
|
+
self.last_print_n = self.initial
|
152
|
+
self.last_print_t = time.time()
|
153
|
+
self.start_t = time.time()
|
154
|
+
self.last_rate = 0
|
155
|
+
self.closed = False
|
156
|
+
|
157
|
+
# Display initial bar if we have total and not disabled
|
158
|
+
if not self.disable and self.total is not None and not self.noninteractive:
|
159
|
+
self._display()
|
160
|
+
|
161
|
+
def _format_rate(self, rate):
|
162
|
+
"""Format rate with proper units and reasonable precision."""
|
163
|
+
if rate <= 0:
|
164
|
+
return ""
|
165
|
+
|
166
|
+
# For bytes with scaling, use binary units
|
167
|
+
if self.unit in ("B", "bytes") and self.unit_scale:
|
168
|
+
for threshold, unit in [(1024**3, "GB/s"), (1024**2, "MB/s"), (1024, "KB/s")]:
|
169
|
+
if rate >= threshold:
|
170
|
+
return f"{rate / threshold:.1f}{unit}"
|
171
|
+
return f"{rate:.1f}B/s"
|
172
|
+
|
173
|
+
# For other scalable units, use decimal units
|
174
|
+
if self.unit_scale and self.unit in ("it", "items", ""):
|
175
|
+
for threshold, prefix in [(1000000, "M"), (1000, "K")]:
|
176
|
+
if rate >= threshold:
|
177
|
+
return f"{rate / threshold:.1f}{prefix}{self.unit}/s"
|
178
|
+
|
179
|
+
# Default formatting
|
180
|
+
precision = ".1f" if rate >= 1 else ".2f"
|
181
|
+
return f"{rate:{precision}}{self.unit}/s"
|
182
|
+
|
183
|
+
def _format_num(self, num):
|
184
|
+
"""Format number with optional unit scaling."""
|
185
|
+
if not self.unit_scale or self.unit not in ("B", "bytes"):
|
186
|
+
return str(num)
|
187
|
+
|
188
|
+
for unit in ["", "K", "M", "G", "T"]:
|
189
|
+
if abs(num) < self.unit_divisor:
|
190
|
+
return f"{num:3.1f}{unit}B" if unit else f"{num:.0f}B"
|
191
|
+
num /= self.unit_divisor
|
192
|
+
return f"{num:.1f}PB"
|
193
|
+
|
194
|
+
def _format_time(self, seconds):
|
195
|
+
"""Format time duration."""
|
196
|
+
if seconds < 60:
|
197
|
+
return f"{seconds:.1f}s"
|
198
|
+
elif seconds < 3600:
|
199
|
+
return f"{int(seconds // 60)}:{seconds % 60:02.0f}"
|
200
|
+
else:
|
201
|
+
h, m = int(seconds // 3600), int((seconds % 3600) // 60)
|
202
|
+
return f"{h}:{m:02d}:{seconds % 60:02.0f}"
|
203
|
+
|
204
|
+
def _generate_bar(self, width=12):
|
205
|
+
"""Generate progress bar."""
|
206
|
+
if self.total is None:
|
207
|
+
return "━" * width if self.closed else "─" * width
|
208
|
+
|
209
|
+
frac = min(1.0, self.n / self.total)
|
210
|
+
filled = int(frac * width)
|
211
|
+
bar = "━" * filled + "─" * (width - filled)
|
212
|
+
if filled < width and frac * width - filled > 0.5:
|
213
|
+
bar = bar[:filled] + "╸" + bar[filled + 1 :]
|
214
|
+
return bar
|
215
|
+
|
216
|
+
def _should_update(self, dt, dn):
|
217
|
+
"""Check if display should update."""
|
218
|
+
if self.noninteractive:
|
219
|
+
return False
|
220
|
+
|
221
|
+
if self.total is not None and self.n >= self.total:
|
222
|
+
return True
|
223
|
+
|
224
|
+
return dt >= self.mininterval
|
225
|
+
|
226
|
+
def _display(self, final=False):
|
227
|
+
"""Display progress bar."""
|
228
|
+
if self.disable or (self.closed and not final):
|
229
|
+
return
|
230
|
+
|
231
|
+
current_time = time.time()
|
232
|
+
dt = current_time - self.last_print_t
|
233
|
+
dn = self.n - self.last_print_n
|
234
|
+
|
235
|
+
if not final and not self._should_update(dt, dn):
|
236
|
+
return
|
237
|
+
|
238
|
+
# Calculate rate (avoid crazy numbers)
|
239
|
+
if dt > self.MIN_RATE_CALC_INTERVAL: # Only calculate rate if enough time has passed
|
240
|
+
rate = dn / dt
|
241
|
+
# Smooth rate for reasonable values, use raw rate for very high values
|
242
|
+
if rate < self.MAX_SMOOTHED_RATE:
|
243
|
+
self.last_rate = self.RATE_SMOOTHING_FACTOR * rate + (1 - self.RATE_SMOOTHING_FACTOR) * self.last_rate
|
244
|
+
rate = self.last_rate
|
245
|
+
else:
|
246
|
+
rate = self.last_rate
|
247
|
+
|
248
|
+
# At completion, use the overall rate for more accurate display
|
249
|
+
if self.n >= (self.total or float("inf")) and self.total and self.total > 0:
|
250
|
+
overall_elapsed = current_time - self.start_t
|
251
|
+
if overall_elapsed > 0:
|
252
|
+
rate = self.n / overall_elapsed
|
253
|
+
|
254
|
+
# Update counters
|
255
|
+
self.last_print_n = self.n
|
256
|
+
self.last_print_t = current_time
|
257
|
+
elapsed = current_time - self.start_t
|
258
|
+
|
259
|
+
# Build progress components
|
260
|
+
if self.total is not None:
|
261
|
+
percentage = (self.n / self.total) * 100
|
262
|
+
# For bytes with unit scaling, avoid repeating units: show "5.4/5.4MB" not "5.4MB/5.4MB"
|
263
|
+
n_fmt = self._format_num(self.n)
|
264
|
+
total_fmt = self._format_num(self.total)
|
265
|
+
if self.unit_scale and self.unit in ("B", "bytes"):
|
266
|
+
n_fmt = n_fmt.rstrip("KMGTPB") # Remove unit suffix from current
|
267
|
+
else:
|
268
|
+
percentage = 0
|
269
|
+
n_fmt = self._format_num(self.n)
|
270
|
+
total_fmt = "?"
|
271
|
+
|
272
|
+
elapsed_str = self._format_time(elapsed)
|
273
|
+
rate_fmt = self._format_rate(rate) or (self._format_rate(self.n / elapsed) if elapsed > 0 else "")
|
274
|
+
|
275
|
+
# Format progress string
|
276
|
+
progress_str = self.bar_format.format(
|
277
|
+
desc=self.desc,
|
278
|
+
percentage=percentage,
|
279
|
+
bar=self._generate_bar(),
|
280
|
+
n_fmt=n_fmt,
|
281
|
+
total_fmt=total_fmt,
|
282
|
+
rate_fmt=rate_fmt,
|
283
|
+
elapsed=elapsed_str,
|
284
|
+
unit=self.unit,
|
285
|
+
)
|
286
|
+
|
287
|
+
# Write to output
|
288
|
+
try:
|
289
|
+
if self.noninteractive:
|
290
|
+
# In non-interactive environments, avoid carriage return which creates empty lines
|
291
|
+
self.file.write(progress_str)
|
292
|
+
else:
|
293
|
+
# In interactive terminals, use carriage return and clear line for updating display
|
294
|
+
self.file.write(f"\r\033[K{progress_str}")
|
295
|
+
self.file.flush()
|
296
|
+
except Exception:
|
297
|
+
pass
|
298
|
+
|
299
|
+
def update(self, n=1):
|
300
|
+
"""Update progress by n steps."""
|
301
|
+
if not self.disable and not self.closed:
|
302
|
+
self.n += n
|
303
|
+
self._display()
|
304
|
+
|
305
|
+
def set_description(self, desc):
|
306
|
+
"""Set description."""
|
307
|
+
self.desc = desc or ""
|
308
|
+
if not self.disable:
|
309
|
+
self._display()
|
310
|
+
|
311
|
+
def set_postfix(self, **kwargs):
|
312
|
+
"""Set postfix (appends to description)."""
|
313
|
+
if kwargs:
|
314
|
+
postfix = ", ".join(f"{k}={v}" for k, v in kwargs.items())
|
315
|
+
base_desc = self.desc.split(" | ")[0] if " | " in self.desc else self.desc
|
316
|
+
self.set_description(f"{base_desc} | {postfix}")
|
317
|
+
|
318
|
+
def close(self):
|
319
|
+
"""Close progress bar."""
|
320
|
+
if self.closed:
|
321
|
+
return
|
322
|
+
|
323
|
+
self.closed = True # Set before final display
|
324
|
+
|
325
|
+
if not self.disable:
|
326
|
+
# Final display
|
327
|
+
if self.total and self.n >= self.total:
|
328
|
+
self.n = self.total
|
329
|
+
self._display(final=True)
|
330
|
+
|
331
|
+
# Cleanup
|
332
|
+
if self.leave:
|
333
|
+
self.file.write("\n")
|
334
|
+
else:
|
335
|
+
self.file.write("\r\033[K")
|
336
|
+
|
337
|
+
try:
|
338
|
+
self.file.flush()
|
339
|
+
except Exception:
|
340
|
+
pass
|
341
|
+
|
342
|
+
def __enter__(self):
|
343
|
+
"""Enter context manager."""
|
344
|
+
return self
|
345
|
+
|
346
|
+
def __exit__(self, *args):
|
347
|
+
"""Exit context manager and close progress bar."""
|
348
|
+
self.close()
|
349
|
+
|
350
|
+
def __iter__(self):
|
351
|
+
"""Iterate over the wrapped iterable with progress updates."""
|
352
|
+
if self.iterable is None:
|
353
|
+
raise TypeError("'NoneType' object is not iterable")
|
354
|
+
|
355
|
+
try:
|
356
|
+
for item in self.iterable:
|
357
|
+
yield item
|
358
|
+
self.update(1)
|
359
|
+
finally:
|
360
|
+
self.close()
|
361
|
+
|
362
|
+
def __del__(self):
|
363
|
+
"""Destructor to ensure cleanup."""
|
364
|
+
try:
|
365
|
+
self.close()
|
366
|
+
except Exception:
|
367
|
+
pass
|
368
|
+
|
369
|
+
def refresh(self):
|
370
|
+
"""Refresh display."""
|
371
|
+
if not self.disable:
|
372
|
+
self._display()
|
373
|
+
|
374
|
+
def clear(self):
|
375
|
+
"""Clear progress bar."""
|
376
|
+
if not self.disable:
|
377
|
+
try:
|
378
|
+
self.file.write("\r\033[K")
|
379
|
+
self.file.flush()
|
380
|
+
except Exception:
|
381
|
+
pass
|
382
|
+
|
383
|
+
@staticmethod
|
384
|
+
def write(s, file=None, end="\n"):
|
385
|
+
"""Static method to write without breaking progress bar."""
|
386
|
+
file = file or sys.stdout
|
387
|
+
try:
|
388
|
+
file.write(s + end)
|
389
|
+
file.flush()
|
390
|
+
except Exception:
|
391
|
+
pass
|
392
|
+
|
393
|
+
|
394
|
+
if __name__ == "__main__":
|
395
|
+
import time
|
396
|
+
|
397
|
+
# Example 1: Basic usage with known total
|
398
|
+
print("1. Basic progress bar with known total:")
|
399
|
+
for i in TQDM(range(20), desc="Known total"):
|
400
|
+
time.sleep(0.05)
|
401
|
+
print()
|
402
|
+
|
403
|
+
# Example 2: Manual updates with known total
|
404
|
+
print("2. Manual updates with known total:")
|
405
|
+
pbar = TQDM(total=30, desc="Manual updates", unit="files")
|
406
|
+
for i in range(30):
|
407
|
+
time.sleep(0.03)
|
408
|
+
pbar.update(1)
|
409
|
+
if i % 10 == 9:
|
410
|
+
pbar.set_description(f"Processing batch {i // 10 + 1}")
|
411
|
+
pbar.close()
|
412
|
+
print()
|
413
|
+
|
414
|
+
# Example 3: Unknown total - this was the problematic case
|
415
|
+
print("3. Progress bar with unknown total:")
|
416
|
+
pbar = TQDM(desc="Unknown total", unit="items")
|
417
|
+
for i in range(25):
|
418
|
+
time.sleep(0.08)
|
419
|
+
pbar.update(1)
|
420
|
+
if i % 5 == 4:
|
421
|
+
pbar.set_postfix(processed=i + 1, status="OK")
|
422
|
+
pbar.close()
|
423
|
+
print()
|
424
|
+
|
425
|
+
# Example 4: Context manager with unknown total
|
426
|
+
print("4. Context manager with unknown total:")
|
427
|
+
with TQDM(desc="Processing stream", unit="B", unit_scale=True, unit_divisor=1024) as pbar:
|
428
|
+
for i in range(30):
|
429
|
+
time.sleep(0.1)
|
430
|
+
pbar.update(1024 * 1024 * i) # Simulate processing MB of data
|
431
|
+
print()
|
432
|
+
|
433
|
+
# Example 5: Generator with unknown length
|
434
|
+
print("5. Iterator with unknown length:")
|
435
|
+
|
436
|
+
def data_stream():
|
437
|
+
"""Simulate a data stream of unknown length."""
|
438
|
+
import random
|
439
|
+
|
440
|
+
for i in range(random.randint(10, 20)):
|
441
|
+
yield f"data_chunk_{i}"
|
442
|
+
|
443
|
+
for chunk in TQDM(data_stream(), desc="Stream processing", unit="chunks"):
|
444
|
+
time.sleep(0.1)
|
445
|
+
print()
|
446
|
+
|
447
|
+
# Example 6: File-like processing simulation
|
448
|
+
print("6. File processing simulation (unknown size):")
|
449
|
+
|
450
|
+
def process_files():
|
451
|
+
"""Simulate processing files of unknown count."""
|
452
|
+
files = [f"file_{i}.txt" for i in range(18)]
|
453
|
+
return files
|
454
|
+
|
455
|
+
pbar = TQDM(desc="Scanning files", unit="files")
|
456
|
+
files = process_files()
|
457
|
+
for i, filename in enumerate(files):
|
458
|
+
time.sleep(0.06)
|
459
|
+
pbar.update(1)
|
460
|
+
pbar.set_description(f"Processing {filename}")
|
461
|
+
pbar.close()
|
462
|
+
print()
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|