ptrack


Logs | Files | README | README | LICENSE | LICENSE | GitLab


1
commit 10e281a67d4d73e059989654db399521dfdb21b1
2
Author: Connor Etherington <[email protected]>
3
Date:   Tue Nov 21 01:16:11 2023 +0200
4
5
    Auto-Commit Update - 20231121
6
---
7
 PKGBUILD                             |   2 +-
8
 README.md                            |   2 +-
9
 build/lib/ptrack/__init__.py         |  16 ---
10
 build/lib/ptrack/main.py             | 215 -----------------------------------
11
 build/lib/ptrack/methods.py          | 147 ------------------------
12
 dist/ptrack-1.0.0-py3-none-any.whl   | Bin 6744 -> 0 bytes
13
 dist/ptrack-1.0.0.tar.gz             | Bin 6921 -> 0 bytes
14
 ptrack.1.gz                          | Bin 808 -> 807 bytes
15
 ptrack.egg-info/PKG-INFO             |  13 ---
16
 ptrack.egg-info/SOURCES.txt          |  12 --
17
 ptrack.egg-info/dependency_links.txt |   1 -
18
 ptrack.egg-info/entry_points.txt     |   5 -
19
 ptrack.egg-info/requires.txt         |   6 -
20
 ptrack.egg-info/top_level.txt        |   1 -
21
 ptrack/__init__.py                   |   3 +-
22
 ptrack/main.py                       | 154 ++++++++++++++++---------
23
 ptrack/media.py                      | 113 ++++++++++++++++++
24
 ptrack/methods.py                    |  54 ++++++++-
25
 ptrack/urlDeconstruct.py             |  85 ++++++++++++++
26
 recipe/meta.yaml                     |   2 +-
27
 requirements.txt                     |   6 +
28
 setup.py                             |   2 +-
29
 22 files changed, 366 insertions(+), 473 deletions(-)
30
31
diff --git a/PKGBUILD b/PKGBUILD
32
index 8a3542f..4714fd3 100644
33
--- a/PKGBUILD
34
+++ b/PKGBUILD
35
@@ -1,7 +1,7 @@
36
 # Maintainer: Connor Etherington <[email protected]>
37
 # ---
38
 pkgname=ptrack
39
-pkgver=1.0.0
40
+pkgver=2.0.0
41
 pkgrel=1
42
 pkgdesc="A simple CLI utility for asthetically tracking progress when copying, moving or downloading files."
43
 arch=(x86_64)
44
diff --git a/README.md b/README.md
45
index 631b844..a3eb98b 100644
46
--- a/README.md
47
+++ b/README.md
48
@@ -3,7 +3,7 @@
49
 ### Welcome to ptrack, a powerful and user-friendly CLI utility for tracking the progress of your file operations.
50
 ### Designed to be concise, efficient and performance-optimized, ptrack works swiftly and accurately, while providing in-depth insight into the progress of the task at hand.
51
 
52
-*Version: 1.0.0*
53
+*Version: 2.0.0*
54
 
55
 ***
56
 
57
diff --git a/build/lib/ptrack/__init__.py b/build/lib/ptrack/__init__.py
58
deleted file mode 100644
59
index bf11915..0000000
60
--- a/build/lib/ptrack/__init__.py
61
+++ /dev/null
62
@@ -1,16 +0,0 @@
63
-import argparse
64
-version="1.0.0"
65
-
66
-parser = argparse.ArgumentParser(description='A simple CLI utility for asthetically tracking progress when copying or moving files.')
67
-parser.add_argument('-v', '--verbose', action='store_true', help='verbose output')
68
-parser.add_argument('-c', '--copy', action='store_true', help='copy files (You can use `ptc` instead of `ptrack -c`)')
69
-parser.add_argument('-m', '--move', action='store_true', help='move files (You can use `ptm` instead of `ptrack -m`)')
70
-parser.add_argument('-d', '--download', action='store_true', help='download files (You can use `ptd` instead of `ptrack -d`)')
71
-parser.add_argument('-V', '--version', action='version', version='%(prog)s' + version)
72
-
73
-args, unknown = parser.parse_known_args()
74
-
75
-verbose = args.verbose
76
-copy = args.copy
77
-move = args.move
78
-download = args.download
79
diff --git a/build/lib/ptrack/main.py b/build/lib/ptrack/main.py
80
deleted file mode 100644
81
index 44e9aa6..0000000
82
--- a/build/lib/ptrack/main.py
83
+++ /dev/null
84
@@ -1,215 +0,0 @@
85
-import os
86
-import re
87
-import sys
88
-import ptrack
89
-from ptrack.methods import format_file_size, regular_copy, verbose_copy, hlp, getTotalSize, CustomFileSizeColumn
90
-from rich.progress import Progress, BarColumn, TextColumn, TimeRemainingColumn, FileSizeColumn
91
-from rich.console import Console
92
-from datetime import timedelta
93
-import shutil
94
-import requests
95
-import validators
96
-
97
-verbose = ptrack.verbose
98
-argCopy = ptrack.copy
99
-argMove = ptrack.move
100
-argDownload = ptrack.download
101
-
102
-
103
-def run(process):
104
-    console = Console()
105
-
106
-    if len(sys.argv) < 3:
107
-        hlp()
108
-        if process == "Copying":
109
-            console.print("[bold cyan]Usage: ptc [OPTIONS] SOURCE... DESTINATION[/bold cyan]")
110
-        elif process == "Moving":
111
-            console.print("[bold cyan]Usage: ptm [OPTIONS] SOURCE... DESTINATION[/bold cyan]")
112
-        sys.exit(1)
113
-
114
-    src_paths = sys.argv[1:-1]
115
-    dst = sys.argv[-1]
116
-    srcPaths = []
117
-
118
-    for path in src_paths:
119
-        if path.endswith('/'):
120
-            path = path[:-1]
121
-        srcPaths.append(path)
122
-
123
-    if os.path.isdir(dst):
124
-        dst_dir = dst
125
-        new_name = None
126
-    else:
127
-        dst_dir = os.path.dirname(dst)
128
-        new_name = os.path.basename(dst)
129
-
130
-    total_files = sum(len(files) for path in srcPaths for r, d, files in os.walk(path) if os.path.isdir(path)) + sum(1 for path in srcPaths if os.path.isfile(path))
131
-    total_size = getTotalSize(srcPaths)
132
-    destination_path = os.path.join(dst_dir, os.path.basename(srcPaths[0]) if not new_name else new_name)
133
-
134
-    current_file = 1
135
-
136
-    if total_files > 1:
137
-        console.print(f"\n[#ea2a6f]{process}:[/#ea2a6f] [bold cyan]{total_files} files[/bold cyan]\n")
138
-    else:
139
-        for src_path in srcPaths:
140
-            if os.path.isfile(src_path):
141
-                console.print(f"\n[#ea2a6f]{process}:[/#ea2a6f] [bold cyan] {os.path.basename(src_path)} [/bold cyan]\n")
142
-
143
-    if verbose:
144
-        for src_path in srcPaths:
145
-            if os.path.isfile(src_path):
146
-                dst_path = os.path.join(dst_dir, os.path.basename(src_path) if not new_name else new_name)
147
-                terminate = verbose_copy(src_path, dst_path, console, current_file, total_files, file_name=os.path.basename(src_path))
148
-                current_file += 1
149
-                if terminate == 'c':
150
-                    console.print("\n[bold red]\[-][/bold red][bold white] Operation cancelled by user.[/bold white]\n")
151
-                    sys.exit(1)
152
-            else:
153
-                for root, dirs, files in os.walk(src_path):
154
-                    for file in files:
155
-                        src_file_path = os.path.join(root, file)
156
-                        relative_path = os.path.relpath(src_file_path, start=src_path)
157
-                        dst_file_path = os.path.join(dst_dir, os.path.basename(src_path) if not new_name else new_name, relative_path)
158
-                        os.makedirs(os.path.dirname(src_file_path), exist_ok=True)
159
-                        terminate = verbose_copy(src_file_path, dst_file_path, console, current_file, total_files, file_name=file)
160
-                        current_file += 1
161
-                        if terminate == 'c':
162
-                            console.print("\n[bold red]\[-][/bold red][bold white] Operation cancelled by user.[/bold white]\n")
163
-                            sys.exit(1)
164
-    else:
165
-        with Progress(
166
-            BarColumn(bar_width=50),
167
-            "[progress.percentage]{task.percentage:>3.0f}%",
168
-            TimeRemainingColumn(),
169
-            "[#ea2a6f][[/#ea2a6f]",
170
-            FileSizeColumn(),
171
-            "[#ea2a6f]/[/#ea2a6f]",
172
-            TextColumn("[bold cyan]{task.fields[total_size]}[/bold cyan]"),
173
-            "[#ea2a6f]][/#ea2a6f]",
174
-            TextColumn("-[bold yellow] {task.fields[current_file_name]}[/bold yellow]"),
175
-            console=console,
176
-            auto_refresh=False
177
-        ) as progress:
178
-            task = progress.add_task("", total=total_size, total_size=format_file_size(total_size), current_file_name="Initializing...")
179
-
180
-            try:
181
-                for src_path in srcPaths:
182
-                    if os.path.isfile(src_path):
183
-                        src_file_path = src_path
184
-                        dst_file_path = os.path.join(dst_dir, os.path.basename(src_path) if not new_name else new_name)
185
-                        file_premissions = os.stat(src_file_path).st_mode
186
-                        progress.update(task, current_file_name=os.path.basename(src_path), refresh=True)  # Force refresh
187
-                        terminate = regular_copy(src_path, dst_file_path, console, task, progress, file_name=os.path.basename(src_path))
188
-                        if terminate == 'c':
189
-                            console.print("\n[bold red]\[-][/bold red][bold white] Operation cancelled by user.[/bold white]\n")
190
-                            sys.exit(1)
191
-                        else:
192
-                            os.chmod(dst_file_path, file_premissions)
193
-
194
-                    else:
195
-                        for root, dirs, files in os.walk(src_path):
196
-                            for file in files:
197
-                                src_file_path = os.path.join(root, file)
198
-                                relative_path = os.path.relpath(src_file_path, start=src_path)
199
-                                dst_file_path = os.path.join(dst_dir, os.path.basename(src_path) if not new_name else new_name, relative_path)
200
-                                os.makedirs(os.path.dirname(dst_file_path), exist_ok=True)
201
-                                progress.update(task, current_file_name=file, refresh=True)  # Force refresh
202
-                                regular_copy(src_file_path, dst_file_path, console, task, progress, file_name=file)
203
-
204
-            except KeyboardInterrupt:
205
-                console.print("\n[bold red]\[-][/bold red][bold white] Operation cancelled by user.[/bold white]\n")
206
-                sys.exit(1)
207
-
208
-    return srcPaths
209
-
210
-
211
-def download():
212
-    console = Console()
213
-    urls = sys.argv[1:]
214
-
215
-    if len(urls) == 0:
216
-        console.print("\n[bold red][-][/bold red] No URL provided.\n")
217
-        sys.exit()
218
-
219
-    num_urls = len(urls)
220
-    for url in urls:
221
-        if url.startswith('-'):
222
-            num_urls -= 1
223
-        elif not validators.url(url):
224
-            console.print(f"\n[bold red][-][/bold red] Invalid URL: [bold yellow]{url}[/bold yellow]\n")
225
-            sys.exit()
226
-
227
-    console.print(f"\n[#ea2a6f]Downloading:[/#ea2a6f] [bold yellow]{num_urls}[/bold yellow] [bold cyan]files[/bold cyan]\n")
228
-
229
-    errors = []
230
-    for url in urls:
231
-        try:
232
-            if url.startswith('-'):
233
-                continue
234
-
235
-            response = requests.get(url, stream=True, allow_redirects=True)
236
-            total_size_in_bytes = int(response.headers.get('content-length', 0))
237
-            content_disposition = response.headers.get('content-disposition')
238
-            destination_path = re.findall('filename="(.+)"', content_disposition)[0] if content_disposition and re.findall('filename="(.+)"', content_disposition) else os.path.basename(url)
239
-
240
-            with Progress(
241
-                BarColumn(bar_width=50),
242
-                "[progress.percentage]{task.percentage:>3.0f}%",
243
-                TimeRemainingColumn(),
244
-                "[#ea2a6f][[/#ea2a6f]",
245
-                CustomFileSizeColumn(),
246
-                "[#ea2a6f]][/#ea2a6f]",
247
-                f" {destination_path}",  # This line will print the filename at the end
248
-                console=console,
249
-                auto_refresh=True
250
-            ) as progress:
251
-                task_id = progress.add_task("Downloading", total=total_size_in_bytes)
252
-                block_size = 1024  # 1 Kibibyte
253
-                with open(destination_path, 'wb') as file:
254
-                    for data in response.iter_content(block_size):
255
-                        file.write(data)
256
-                        progress.update(task_id, advance=block_size)
257
-        except KeyboardInterrupt:
258
-            console.print("\n[bold red]\[-][/bold red][bold white] Operation cancelled by user.[/bold white]\n")
259
-            sys.exit(1)
260
-
261
-        except Exception as e:
262
-            console.print(f"\n[bold red]\[-][/bold red][bold white] Could not download file: [bold yellow]{url}[/bold yellow]\n")
263
-            print(e)
264
-            errors.append(url)
265
-
266
-    if len(errors) == 0:
267
-        console.print("\n[bold green]Download completed![/bold green]\n")
268
-    else:
269
-        console.print("[bold red]The following files could not be downloaded:[/bold red]\n")
270
-        for error in errors:
271
-            console.print(f"[bold red]   -[/bold red][bold yellow]{error}[/bold yellow]\n")
272
-
273
-
274
-def copy():
275
-    run('Copying')
276
-
277
-
278
-def move():
279
-    src_paths = run('Moving')
280
-    for src_path in src_paths:
281
-        if os.path.isfile(src_path):
282
-            os.remove(src_path)
283
-        else:
284
-            shutil.rmtree(src_path)
285
-
286
-
287
-def main():
288
-    if argMove:
289
-        move()
290
-    elif argCopy:
291
-        copy()
292
-    elif argDownload:
293
-        download()
294
-    else:
295
-        hlp()
296
-
297
-
298
-if __name__ == "__main__":
299
-    main()
300
diff --git a/build/lib/ptrack/methods.py b/build/lib/ptrack/methods.py
301
deleted file mode 100644
302
index 01a2d63..0000000
303
--- a/build/lib/ptrack/methods.py
304
+++ /dev/null
305
@@ -1,147 +0,0 @@
306
-import os
307
-import sys
308
-import requests
309
-from rich.console import Console
310
-from rich.progress import Progress, TextColumn, BarColumn, TimeRemainingColumn, FileSizeColumn, Task, DownloadColumn, TimeElapsedColumn
311
-from rich.text import Text
312
-from datetime import timedelta
313
-from humanize import naturalsize
314
-import shutil
315
-
316
-console = Console()
317
-operation_cancelled = False
318
-
319
-
320
-def getTotalSize(srcPaths):
321
-    total_size = 0
322
-    for path in srcPaths:
323
-        if os.path.isfile(path):
324
-            total_size += os.path.getsize(path)
325
-        else:
326
-            for r, d, files in os.walk(path):
327
-                for f in files:
328
-                    fp = os.path.join(r, f)
329
-                    total_size += os.path.getsize(fp)
330
-    return total_size
331
-
332
-
333
-def format_file_size(file_size):
334
-    if file_size >= 1000 ** 4:  # Terabyte
335
-        return f"{round(file_size / (1000 ** 4))} TB"
336
-    elif file_size >= 1000 ** 3:  # Gigabyte
337
-        return f"{round(file_size / (1000 ** 3))} GB"
338
-    elif file_size >= 1000 ** 2:  # Megabyte
339
-        return f"{round(file_size / (1000 ** 2))} MB"
340
-    elif file_size >= 1000:  # Kilobyte
341
-        return f"{round(file_size / 1000)} kB"
342
-    else:  # Byte
343
-        return f"{file_size} bytes"
344
-
345
-
346
-
347
-def regular_copy(src, dst, console, task, progress, file_name):
348
-
349
-    global operation_cancelled
350
-
351
-    try:
352
-        with open(src, 'rb') as fsrc, open(dst, 'wb') as fdst:
353
-            while True:
354
-                buf = fsrc.read(1024*1024)
355
-                if not buf or operation_cancelled:
356
-                    break
357
-                filePremissions = os.stat(src).st_mode
358
-                fdst.write(buf)
359
-                progress.update(task, advance=len(buf))
360
-                progress.refresh()
361
-
362
-                os.chmod(dst, filePremissions)
363
-
364
-    except KeyboardInterrupt:
365
-        operation_cancelled = True
366
-        progress.stop()
367
-        return "c"
368
-
369
-
370
-def verbose_copy(src, dst, console, current, total_files, file_name):
371
-    operation_cancelled = False
372
-    file_size = os.path.getsize(src)
373
-
374
-    with Progress(
375
-        BarColumn(bar_width=50),
376
-        "[progress.percentage]{task.percentage:>3.0f}%",
377
-        TimeRemainingColumn(),
378
-        "[#ea2a6f][[/#ea2a6f]",
379
-        FileSizeColumn(),
380
-        "[#ea2a6f]/[/#ea2a6f]",
381
-        TextColumn(f"[bold cyan]{format_file_size(file_size)}[/bold cyan]"),
382
-        "[#ea2a6f]][/#ea2a6f]",
383
-        f"({current} of {total_files}) - {file_name}",
384
-        console=console,
385
-        auto_refresh=False
386
-    ) as progress:
387
-        task = progress.add_task("", total=file_size, file_size=format_file_size(file_size))
388
-
389
-        try:
390
-            with open(src, 'rb') as fsrc, open(dst, 'wb') as fdst:
391
-                while not progress.finished:
392
-                    buf = fsrc.read(1024*1024)
393
-                    if not buf or operation_cancelled:
394
-                        break
395
-                    fdst.write(buf)
396
-                    progress.update(task, advance=len(buf))
397
-                    progress.refresh()
398
-
399
-                    shutil.copystat(src, dst)
400
-
401
-        except KeyboardInterrupt:
402
-            operation_cancelled = True
403
-            progress.stop()
404
-            return "c"
405
-
406
-
407
-def hlp():
408
-    print("""
409
-usage: ptrack [-h] [-v] [-c] [-m] [-d] [-V]
410
-
411
-A simple CLI utility for asthetically tracking progress when copying or moving files.
412
-
413
-options:
414
-  -h, --help      show this help message and exit
415
-  -v, --verbose   verbose output
416
-  -c, --copy      copy files (You can use `ptc` instead of `ptrack -c`)
417
-  -m, --move      move files (You can use `ptm` instead of `ptrack -m`)
418
-  -d, --download  download files (You can use `ptd` instead of `ptrack -d`)
419
-  -V, --version   show program's version number and exit
420
-""")
421
-
422
-
423
-class CustomFileSizeColumn(FileSizeColumn, TimeElapsedColumn):
424
-    def render(self, task):
425
-        completed = task.completed
426
-        total = task.total
427
-        elapsed = task.elapsed
428
-
429
-        if elapsed > 0.0:  # Prevent division by zero
430
-            download_speed = completed / elapsed  # calculate download rate
431
-        else:
432
-            download_speed = 0
433
-
434
-        if total:
435
-            size = Text.assemble(
436
-                (f"{self._human_readable_size(completed)}", "green"),  # completed
437
-                (" / ", "none"),  # separator
438
-                (f"{self._human_readable_size(total)}", "red"),  # total
439
-                (" [", "none"),  # opening square bracket
440
-                (f"{self._human_readable_size(download_speed)}/s", "blue"),  # download rate
441
-                ("]", "none"),  # closing square bracket
442
-            )
443
-        else:
444
-            size = Text(str(self._human_readable_size(completed)))
445
-        return size
446
-
447
-    def _human_readable_size(self, size: int) -> str:
448
-        for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
449
-            if abs(size) < 1024.0:
450
-                return f"{size:.1f}{unit}"
451
-            size /= 1024.0
452
-        return f"{size:.1f}PB"
453
diff --git a/dist/ptrack-1.0.0-py3-none-any.whl b/dist/ptrack-1.0.0-py3-none-any.whl
454
deleted file mode 100644
455
index c1fa573..0000000
456
Binary files a/dist/ptrack-1.0.0-py3-none-any.whl and /dev/null differ
457
diff --git a/dist/ptrack-1.0.0.tar.gz b/dist/ptrack-1.0.0.tar.gz
458
deleted file mode 100644
459
index c127219..0000000
460
Binary files a/dist/ptrack-1.0.0.tar.gz and /dev/null differ
461
diff --git a/ptrack.1.gz b/ptrack.1.gz
462
index 4833759..2933b5f 100644
463
Binary files a/ptrack.1.gz and b/ptrack.1.gz differ
464
diff --git a/ptrack.egg-info/PKG-INFO b/ptrack.egg-info/PKG-INFO
465
deleted file mode 100644
466
index e28cefc..0000000
467
--- a/ptrack.egg-info/PKG-INFO
468
+++ /dev/null
469
@@ -1,13 +0,0 @@
470
-Metadata-Version: 2.1
471
-Name: ptrack
472
-Version: 1.0.0
473
-Summary: A simple CLI utility for asthetically tracking progress when copying, moving or downloading files.
474
-Author: Connor Etherington
475
-Author-email: [email protected]
476
-License-File: LICENSE
477
-Requires-Dist: rich
478
-Requires-Dist: argparse
479
-Requires-Dist: requests
480
-Requires-Dist: validators
481
-Requires-Dist: setuptools
482
-Requires-Dist: humanize
483
diff --git a/ptrack.egg-info/SOURCES.txt b/ptrack.egg-info/SOURCES.txt
484
deleted file mode 100644
485
index 086a784..0000000
486
--- a/ptrack.egg-info/SOURCES.txt
487
+++ /dev/null
488
@@ -1,12 +0,0 @@
489
-LICENSE
490
-README.md
491
-setup.py
492
-ptrack/__init__.py
493
-ptrack/main.py
494
-ptrack/methods.py
495
-ptrack.egg-info/PKG-INFO
496
-ptrack.egg-info/SOURCES.txt
497
-ptrack.egg-info/dependency_links.txt
498
-ptrack.egg-info/entry_points.txt
499
-ptrack.egg-info/requires.txt
500
-ptrack.egg-info/top_level.txt
501
diff --git a/ptrack.egg-info/dependency_links.txt b/ptrack.egg-info/dependency_links.txt
502
deleted file mode 100644
503
index 8b13789..0000000
504
--- a/ptrack.egg-info/dependency_links.txt
505
+++ /dev/null
506
@@ -1 +0,0 @@
507
-
508
diff --git a/ptrack.egg-info/entry_points.txt b/ptrack.egg-info/entry_points.txt
509
deleted file mode 100644
510
index ea851b3..0000000
511
--- a/ptrack.egg-info/entry_points.txt
512
+++ /dev/null
513
@@ -1,5 +0,0 @@
514
-[console_scripts]
515
-ptc = ptrack.main:copy
516
-ptd = ptrack.main:download
517
-ptm = ptrack.main:move
518
-ptrack = ptrack.main:main
519
diff --git a/ptrack.egg-info/requires.txt b/ptrack.egg-info/requires.txt
520
deleted file mode 100644
521
index d19a378..0000000
522
--- a/ptrack.egg-info/requires.txt
523
+++ /dev/null
524
@@ -1,6 +0,0 @@
525
-rich
526
-argparse
527
-requests
528
-validators
529
-setuptools
530
-humanize
531
diff --git a/ptrack.egg-info/top_level.txt b/ptrack.egg-info/top_level.txt
532
deleted file mode 100644
533
index c003217..0000000
534
--- a/ptrack.egg-info/top_level.txt
535
+++ /dev/null
536
@@ -1 +0,0 @@
537
-ptrack
538
diff --git a/ptrack/__init__.py b/ptrack/__init__.py
539
index bf11915..7e46d28 100644
540
--- a/ptrack/__init__.py
541
+++ b/ptrack/__init__.py
542
@@ -1,5 +1,5 @@
543
 import argparse
544
-version="1.0.0"
545
+version="2.0.0"
546
 
547
 parser = argparse.ArgumentParser(description='A simple CLI utility for asthetically tracking progress when copying or moving files.')
548
 parser.add_argument('-v', '--verbose', action='store_true', help='verbose output')
549
@@ -14,3 +14,4 @@ verbose = args.verbose
550
 copy = args.copy
551
 move = args.move
552
 download = args.download
553
+
554
diff --git a/ptrack/main.py b/ptrack/main.py
555
index 44e9aa6..c7c6938 100644
556
--- a/ptrack/main.py
557
+++ b/ptrack/main.py
558
@@ -2,20 +2,24 @@ import os
559
 import re
560
 import sys
561
 import ptrack
562
-from ptrack.methods import format_file_size, regular_copy, verbose_copy, hlp, getTotalSize, CustomFileSizeColumn
563
+from ptrack.methods import format_file_size, regular_copy, verbose_copy, hlp, getTotalSize, CustomFileSizeColumn, isMediaUrl, CustomDLColumn
564
+from ptrack.media import mediaDownload
565
 from rich.progress import Progress, BarColumn, TextColumn, TimeRemainingColumn, FileSizeColumn
566
 from rich.console import Console
567
 from datetime import timedelta
568
+from concurrent.futures import ThreadPoolExecutor, as_completed
569
 import shutil
570
 import requests
571
 import validators
572
+from threading import Lock
573
+
574
+lock = Lock()
575
 
576
 verbose = ptrack.verbose
577
 argCopy = ptrack.copy
578
 argMove = ptrack.move
579
 argDownload = ptrack.download
580
 
581
-
582
 def run(process):
583
     console = Console()
584
 
585
@@ -45,7 +49,6 @@ def run(process):
586
 
587
     total_files = sum(len(files) for path in srcPaths for r, d, files in os.walk(path) if os.path.isdir(path)) + sum(1 for path in srcPaths if os.path.isfile(path))
588
     total_size = getTotalSize(srcPaths)
589
-    destination_path = os.path.join(dst_dir, os.path.basename(srcPaths[0]) if not new_name else new_name)
590
 
591
     current_file = 1
592
 
593
@@ -78,7 +81,7 @@ def run(process):
594
                             console.print("\n[bold red]\[-][/bold red][bold white] Operation cancelled by user.[/bold white]\n")
595
                             sys.exit(1)
596
     else:
597
-        with Progress(
598
+        columns= [
599
             BarColumn(bar_width=50),
600
             "[progress.percentage]{task.percentage:>3.0f}%",
601
             TimeRemainingColumn(),
602
@@ -87,35 +90,50 @@ def run(process):
603
             "[#ea2a6f]/[/#ea2a6f]",
604
             TextColumn("[bold cyan]{task.fields[total_size]}[/bold cyan]"),
605
             "[#ea2a6f]][/#ea2a6f]",
606
-            TextColumn("-[bold yellow] {task.fields[current_file_name]}[/bold yellow]"),
607
-            console=console,
608
-            auto_refresh=False
609
-        ) as progress:
610
+            "[bold purple] - [/bold purple]",
611
+            TextColumn("[bold yellow]{task.fields[current_file_name]}[/bold yellow]", justify="left"),
612
+        ]
613
+
614
+        with Progress(*columns, console=console, auto_refresh=False) as progress:
615
+
616
             task = progress.add_task("", total=total_size, total_size=format_file_size(total_size), current_file_name="Initializing...")
617
 
618
+            def threaded_copy(src, dst, file_permissions, console, task, progress):
619
+                terminate = regular_copy(src, dst, console, task, progress, lock)
620
+                if terminate != 'c':
621
+                    os.chmod(dst, file_permissions)
622
+
623
             try:
624
-                for src_path in srcPaths:
625
-                    if os.path.isfile(src_path):
626
-                        src_file_path = src_path
627
-                        dst_file_path = os.path.join(dst_dir, os.path.basename(src_path) if not new_name else new_name)
628
-                        file_premissions = os.stat(src_file_path).st_mode
629
-                        progress.update(task, current_file_name=os.path.basename(src_path), refresh=True)  # Force refresh
630
-                        terminate = regular_copy(src_path, dst_file_path, console, task, progress, file_name=os.path.basename(src_path))
631
-                        if terminate == 'c':
632
-                            console.print("\n[bold red]\[-][/bold red][bold white] Operation cancelled by user.[/bold white]\n")
633
-                            sys.exit(1)
634
+                with ThreadPoolExecutor() as executor:
635
+                    futures = []
636
+                    for src_path in srcPaths:
637
+                        if os.path.isfile(src_path):
638
+                            src_file_path = src_path
639
+                            dst_file_path = os.path.join(dst_dir, os.path.basename(src_path) if not new_name else new_name)
640
+                            file_permissions = os.stat(src_file_path).st_mode
641
+                            progress.update(task, current_file_name=os.path.basename(src_path), refresh=True)
642
+                            future = executor.submit(threaded_copy, src_path, dst_file_path, file_permissions, console, task, progress)
643
+                            futures.append(future)
644
+
645
+                        for future in as_completed(futures):
646
+                            with lock:
647
+                                progress.update(task, advance=future.result())
648
+
649
                         else:
650
-                            os.chmod(dst_file_path, file_premissions)
651
-
652
-                    else:
653
-                        for root, dirs, files in os.walk(src_path):
654
-                            for file in files:
655
-                                src_file_path = os.path.join(root, file)
656
-                                relative_path = os.path.relpath(src_file_path, start=src_path)
657
-                                dst_file_path = os.path.join(dst_dir, os.path.basename(src_path) if not new_name else new_name, relative_path)
658
-                                os.makedirs(os.path.dirname(dst_file_path), exist_ok=True)
659
-                                progress.update(task, current_file_name=file, refresh=True)  # Force refresh
660
-                                regular_copy(src_file_path, dst_file_path, console, task, progress, file_name=file)
661
+                            for root, dirs, files in os.walk(src_path):
662
+                                for file in files:
663
+                                    src_file_path = os.path.join(root, file)
664
+                                    relative_path = os.path.relpath(src_file_path, start=src_path)
665
+                                    dst_file_path = os.path.join(dst_dir, os.path.basename(src_path) if not new_name else new_name, relative_path)
666
+                                    os.makedirs(os.path.dirname(dst_file_path), exist_ok=True)
667
+                                    file_permissions = os.stat(src_file_path).st_mode
668
+                                    progress.update(task, current_file_name=file, refresh=True)
669
+                                    future = executor.submit(threaded_copy, src_path, dst_file_path, file_permissions, console, task, progress)
670
+                                    futures.append(future)
671
+
672
+                                for future in as_completed(futures):
673
+                                    with lock:
674
+                                        progress.update(task, advance=future.result())
675
 
676
             except KeyboardInterrupt:
677
                 console.print("\n[bold red]\[-][/bold red][bold white] Operation cancelled by user.[/bold white]\n")
678
@@ -147,29 +165,63 @@ def download():
679
         try:
680
             if url.startswith('-'):
681
                 continue
682
+            else:
683
+                downloaded_size = 0
684
+                total_size = 0
685
+
686
+                custom_columns = [
687
+                    BarColumn(bar_width=50),
688
+                    "[progress.percentage]{task.percentage:>3.0f}%",
689
+                    TimeRemainingColumn(),
690
+                    "[#ea2a6f][[/#ea2a6f]",
691
+                    FileSizeColumn(),
692
+                    "[#ea2a6f]/[/#ea2a6f]",
693
+                    TextColumn(f"[bold cyan]{total_size}[/bold cyan]"),
694
+                    "[#ea2a6f]][/#ea2a6f]",
695
+                    TextColumn(f"[bold purple] - [/bold purple][bold yellow]{url}[/bold yellow][bold purple] | [/bold purple]Processing filetype...", justify="left"),
696
+                ]
697
+
698
+                with Progress(*custom_columns) as progress:
699
+                    task = progress.add_task("", total=100, file_size="0 KB", start=False)
700
+
701
+                    if isMediaUrl(url):
702
+                        def wipe():
703
+                            sys.stdout.write("\033[F")  # Cursor up one line
704
+                            sys.stdout.write("\033[K")  # Clear to the end of line
705
+                            sys.stdout.flush()
706
+                            sys.stdout.write("\033[F")  # Cursor up one line
707
+
708
+                        mediaDownload(url, progress, wipe)
709
+                        continue
710
+
711
+                response = requests.get(url, stream=True, allow_redirects=True)
712
+                total_size_in_bytes = int(response.headers.get('content-length', 0))
713
+                content_disposition = response.headers.get('content-disposition')
714
+                destination_path = re.findall('filename="(.+)"', content_disposition)[0] if content_disposition and re.findall('filename="(.+)"', content_disposition) else os.path.basename(url)
715
+
716
+                size = format_file_size(total_size_in_bytes)
717
+
718
+                with Progress(
719
+                    BarColumn(bar_width=50),
720
+                    "[progress.percentage]{task.percentage:>3.0f}%",
721
+                    TimeRemainingColumn(),
722
+                    "[#ea2a6f][[/#ea2a6f]",
723
+                    FileSizeColumn(),
724
+                    "[#ea2a6f]/[/#ea2a6f]",
725
+                    TextColumn(f"[bold cyan]{size}[/bold cyan]"),
726
+                    "[#ea2a6f]][/#ea2a6f]",
727
+                    "[bold purple] - [/bold purple]",
728
+                    TextColumn(f"[bold yellow]{destination_path}[/bold yellow]", justify="left"),
729
+                    console=console,
730
+                    auto_refresh=True
731
+                ) as progress:
732
+                    task_id = progress.add_task("Downloading", total=total_size_in_bytes)
733
+                    block_size = 1024  # 1 Kibibyte
734
+                    with open(destination_path, 'wb') as file:
735
+                        for data in response.iter_content(block_size):
736
+                            file.write(data)
737
+                            progress.update(task_id, advance=block_size)
738
 
739
-            response = requests.get(url, stream=True, allow_redirects=True)
740
-            total_size_in_bytes = int(response.headers.get('content-length', 0))
741
-            content_disposition = response.headers.get('content-disposition')
742
-            destination_path = re.findall('filename="(.+)"', content_disposition)[0] if content_disposition and re.findall('filename="(.+)"', content_disposition) else os.path.basename(url)
743
-
744
-            with Progress(
745
-                BarColumn(bar_width=50),
746
-                "[progress.percentage]{task.percentage:>3.0f}%",
747
-                TimeRemainingColumn(),
748
-                "[#ea2a6f][[/#ea2a6f]",
749
-                CustomFileSizeColumn(),
750
-                "[#ea2a6f]][/#ea2a6f]",
751
-                f" {destination_path}",  # This line will print the filename at the end
752
-                console=console,
753
-                auto_refresh=True
754
-            ) as progress:
755
-                task_id = progress.add_task("Downloading", total=total_size_in_bytes)
756
-                block_size = 1024  # 1 Kibibyte
757
-                with open(destination_path, 'wb') as file:
758
-                    for data in response.iter_content(block_size):
759
-                        file.write(data)
760
-                        progress.update(task_id, advance=block_size)
761
         except KeyboardInterrupt:
762
             console.print("\n[bold red]\[-][/bold red][bold white] Operation cancelled by user.[/bold white]\n")
763
             sys.exit(1)
764
diff --git a/ptrack/media.py b/ptrack/media.py
765
new file mode 100644
766
index 0000000..8699ada
767
--- /dev/null
768
+++ b/ptrack/media.py
769
@@ -0,0 +1,113 @@
770
+
771
+import subprocess
772
+from ptrack.urlDeconstruct import get_urls
773
+from ptrack.methods import CustomFileSizeColumn, format_file_size
774
+import ptrack
775
+from rich.progress import Progress, TextColumn, BarColumn, TimeRemainingColumn, Task, DownloadColumn, TimeElapsedColumn, FileSizeColumn
776
+from rich.console import Console
777
+import threading
778
+import time
779
+import os
780
+
781
+console = Console()
782
+
783
+def run_ffmpeg(ffmpeg_command):
784
+    with subprocess.Popen(ffmpeg_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) as process:
785
+        while True:
786
+            line = process.stderr.readline()
787
+            if not line:
788
+                break
789
+
790
+def get_file_size(filename):
791
+    return os.path.getsize(filename) if os.path.exists(filename) else 0
792
+
793
+
794
+#def FileSizeColumn(file):
795
+#    global file_size
796
+#    file_size = get_file_size(file) if os.path.exists(file) else 0
797
+#
798
+#    return f"[bold green]{format_file_size(file_size)}[/bold green]"
799
+
800
+
801
+def download_with_progress(ffmpeg_command, title, size, accuracy, progress, wipe):
802
+    output_file = ffmpeg_command[-1]
803
+    downloaded_size = format_file_size(get_file_size(output_file))
804
+    total_size = format_file_size(size)
805
+
806
+    if not accuracy:
807
+        title = f"[bold purple]- [/bold purple]⚠  [#ea2a6f]Filesize may be inaccurate[/#ea2a6f][bold purple] - [/bold purple][bold yellow]{title}[/bold yellow]"
808
+    else:
809
+        title = f"[bold purple]- [/bold purple][bold yellow]{title}[/bold yellow]"
810
+
811
+    ffmpeg_command.extend(['-flush_packets', '1'])
812
+
813
+    custom_columns = [
814
+        BarColumn(bar_width=50),
815
+        "[progress.percentage]{task.percentage:>3.0f}%",
816
+        TimeRemainingColumn(),
817
+        "[#ea2a6f][[/#ea2a6f]",
818
+        FileSizeColumn(),
819
+        "[#ea2a6f]/[/#ea2a6f]",
820
+        TextColumn(f"[bold cyan]{total_size}[/bold cyan]"),
821
+        "[#ea2a6f]][/#ea2a6f]",
822
+        TextColumn(title, justify="left"),
823
+    ]
824
+
825
+    progress.stop()
826
+    wipe()
827
+
828
+    with Progress(*custom_columns) as progress:
829
+        task = progress.add_task("", total=size, downloaded_size=format_file_size(get_file_size(output_file)))
830
+        process = subprocess.Popen(ffmpeg_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
831
+
832
+        while True:
833
+            output = process.stderr.readline()
834
+            if output == '' and process.poll() is not None:
835
+                break
836
+            if output:
837
+                progress.update(task, completed=get_file_size(output_file))
838
+
839
+            time.sleep(0.3)
840
+
841
+        if process.returncode == 0:
842
+            progress.columns = list(progress.columns)
843
+            progress.columns[-1].renderable = TextColumn(f"[bold yellow]{title}i[/bold yellow][bold green] ✓[/bold green]")
844
+            progress.columns = tuple(progress.columns)
845
+        else:
846
+            progress.columns = list(progress.columns)
847
+            progress.columns[-1].renderable = TextColumn(f"[bold yellow]{title}[/bold yellow][bold red] ✗[/bold red]")
848
+            progress.columns = tuple(progress.columns)
849
+
850
+
851
+
852
+        progress.stop()
853
+
854
+
855
+def mediaDownload(url, progress, wipe):
856
+
857
+    fetched_urls = get_urls([url])
858
+
859
+    print()
860
+
861
+    for fetched_url in fetched_urls:
862
+        if fetched_url:
863
+            video_url, audio_url, title, size, accuracy = fetched_url
864
+            safe_name = make_filename_safe(title)
865
+            ffmpeg_command = [
866
+                'ffmpeg', '-i', video_url, '-i', audio_url,
867
+                '-c:v', 'libx264', '-c:a', 'aac', '-strict', 'experimental',
868
+                '-b:v', '1M', '-b:a', '128k', f'{safe_name}.mp4'
869
+            ] if video_url.endswith('.m3u8') else [
870
+                'ffmpeg', '-i', video_url, '-i', audio_url,
871
+                '-c:v', 'copy', '-c:a', 'copy', f'{safe_name}.mp4'
872
+            ]
873
+
874
+            download_with_progress(ffmpeg_command, title, size, accuracy, progress, wipe)
875
+        else:
876
+            print("Error: Unable to fetch URLs for one of the videos.")
877
+
878
+
879
+
880
+def make_filename_safe(name):
881
+    return name.replace(' ', '_').replace('(', '').replace(')', '').replace('/', '_')
882
+
883
diff --git a/ptrack/methods.py b/ptrack/methods.py
884
index 01a2d63..d72319e 100644
885
--- a/ptrack/methods.py
886
+++ b/ptrack/methods.py
887
@@ -6,7 +6,15 @@ from rich.progress import Progress, TextColumn, BarColumn, TimeRemainingColumn,
888
 from rich.text import Text
889
 from datetime import timedelta
890
 from humanize import naturalsize
891
+from bs4 import BeautifulSoup
892
 import shutil
893
+import mimetypes
894
+import re
895
+from concurrent.futures import ThreadPoolExecutor
896
+import subprocess
897
+import shlex
898
+from functools import lru_cache
899
+
900
 
901
 console = Console()
902
 operation_cancelled = False
903
@@ -75,7 +83,7 @@ def verbose_copy(src, dst, console, current, total_files, file_name):
904
         "[#ea2a6f]/[/#ea2a6f]",
905
         TextColumn(f"[bold cyan]{format_file_size(file_size)}[/bold cyan]"),
906
         "[#ea2a6f]][/#ea2a6f]",
907
-        f"({current} of {total_files}) - {file_name}",
908
+        f"({current} of {total_files})[bold purple] - [/bold purple][bold yellow]{file_name}[/bold yellow]",
909
         console=console,
910
         auto_refresh=False
911
     ) as progress:
912
@@ -145,3 +153,47 @@ class CustomFileSizeColumn(FileSizeColumn, TimeElapsedColumn):
913
                 return f"{size:.1f}{unit}"
914
             size /= 1024.0
915
         return f"{size:.1f}PB"
916
+
917
+class CustomDLColumn(FileSizeColumn, TimeElapsedColumn):
918
+    def render(self, task):
919
+        completed = task.completed
920
+        total = task.total
921
+        elapsed = task.elapsed
922
+
923
+        if elapsed > 0.0:  # Prevent division by zero
924
+            download_speed = completed / elapsed  # calculate download rate
925
+        else:
926
+            download_speed = 0
927
+
928
+        if total:
929
+            size = Text.assemble(
930
+                (f"{self._human_readable_size(completed)}", "green"),  # completed
931
+                (" / ", "none"),  # separator
932
+                (f"{self._human_readable_size(total)}", "red"),  # total
933
+                (" [", "none"),  # opening square bracket
934
+                (f"{self._human_readable_size(download_speed)}/s", "blue"),  # download rate
935
+                ("]", "none"),  # closing square bracket
936
+            )
937
+        else:
938
+            size = Text(str(self._human_readable_size(completed)))
939
+        return size
940
+
941
+    def _human_readable_size(self, size: int) -> str:
942
+        for unit in ['B', 'KB', 'MB', 'GB', 'TB']:
943
+            if abs(size) < 1024.0:
944
+                return f"{size:.1f}{unit}"
945
+            size /= 1024.0
946
+        return f"{size:.1f}PB"
947
+
948
+
949
+def isMediaUrl(url):
950
+    command = shlex.split(f'yt-dlp --get-url {url}')
951
+    try:
952
+        process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
953
+        stdout, stderr = process.communicate()
954
+        return process.returncode == 0 and bool(stdout.strip())
955
+    except Exception as e:
956
+        print(f"Error checking URL type: {e}")
957
+        return False
958
+
959
+
960
diff --git a/ptrack/urlDeconstruct.py b/ptrack/urlDeconstruct.py
961
new file mode 100644
962
index 0000000..23f9c89
963
--- /dev/null
964
+++ b/ptrack/urlDeconstruct.py
965
@@ -0,0 +1,85 @@
966
+import requests
967
+from bs4 import BeautifulSoup
968
+import subprocess
969
+from tempfile import NamedTemporaryFile
970
+from yt_dlp import main as yt_dlp
971
+import json
972
+import ascii_magic
973
+import tkinter as tk
974
+from PIL import Image, ImageTk
975
+from io import BytesIO
976
+from urllib3 import PoolManager
977
+
978
+
979
+def getData(url):
980
+    process = subprocess.Popen(['yt-dlp', '--format',  'bestvideo+bestaudio/best', url, '--dump-json'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
981
+    out, err = process.communicate()
982
+    if err:
983
+        print(f"Error: {err}")
984
+        return None
985
+    try:
986
+        data = json.loads(out)
987
+        return data
988
+    except json.JSONDecodeError:
989
+        print("Failed to decode JSON from yt-dlp output.")
990
+
991
+
992
+
993
+def download_image(url):
994
+    response = requests.get(url)
995
+    if response.status_code == 200:
996
+        with NamedTemporaryFile(delete=False, suffix='.png') as img_temp:
997
+            img = Image.open(requests.get(url, stream=True).raw)
998
+            img.save(img_temp, 'PNG')
999
+            return img_temp.name