ytfetch 1.2.0__tar.gz → 2.0.0__tar.gz
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.
- {ytfetch-1.2.0 → ytfetch-2.0.0}/PKG-INFO +8 -22
- {ytfetch-1.2.0 → ytfetch-2.0.0}/README-pypi.md +10 -25
- {ytfetch-1.2.0 → ytfetch-2.0.0}/README.md +7 -21
- {ytfetch-1.2.0 → ytfetch-2.0.0}/pyproject.toml +1 -1
- {ytfetch-1.2.0 → ytfetch-2.0.0}/src/ytfetch/cli.py +17 -18
- {ytfetch-1.2.0 → ytfetch-2.0.0}/src/ytfetch/formatter.py +14 -4
- ytfetch-2.0.0/src/ytfetch/main.py +97 -0
- ytfetch-2.0.0/src/ytfetch/utils.py +27 -0
- ytfetch-1.2.0/src/ytfetch/main.py +0 -160
- ytfetch-1.2.0/src/ytfetch/utils.py +0 -70
- {ytfetch-1.2.0 → ytfetch-2.0.0}/.gitattributes +0 -0
- {ytfetch-1.2.0 → ytfetch-2.0.0}/.github/workflows/publish.yaml +0 -0
- {ytfetch-1.2.0 → ytfetch-2.0.0}/.gitignore +0 -0
- {ytfetch-1.2.0 → ytfetch-2.0.0}/LICENSE +0 -0
- {ytfetch-1.2.0 → ytfetch-2.0.0}/src/ytfetch/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ytfetch
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0
|
|
4
4
|
Summary: A wrapper for yt-dlp with custom simple fetching flags
|
|
5
5
|
Project-URL: Homepage, https://github.com/voidsnax/ytFetch
|
|
6
6
|
Author: voidsnax
|
|
@@ -14,11 +14,10 @@ Description-Content-Type: text/markdown
|
|
|
14
14
|
|
|
15
15
|
# ytfetch
|
|
16
16
|
|
|
17
|
-
**ytfetch** simplifies media downloading and playlist management by adding
|
|
17
|
+
**ytfetch** simplifies media downloading and playlist management by adding playlist searching, and streamlined format handling on top of the powerful [yt-dlp](https://github.com/yt-dlp/yt-dlp) engine.
|
|
18
18
|
|
|
19
19
|
## ✨ Features
|
|
20
20
|
|
|
21
|
-
* **Smart Fetching:** Apply limits to multiple playlists or map specific ranges in a single download.
|
|
22
21
|
* **Search:** Use `-list` to display or search across single or multiple playlists contents.
|
|
23
22
|
* **Direct Download:** Single flag to download in legacy `avc/h.264` and `mp3` format and audio only extraction.
|
|
24
23
|
* **Video Quality:** Mention preffered video quality directly.
|
|
@@ -68,7 +67,6 @@ Download builds from [here](https://ffmpeg.org/download.html)
|
|
|
68
67
|
Check this [guide](https://github.com/yt-dlp/yt-dlp/wiki/EJS) for more info.
|
|
69
68
|
Also check [yt-dlp dependency](https://github.com/yt-dlp/yt-dlp#dependencies) for optional dependencies.
|
|
70
69
|
|
|
71
|
-
*Note: `yt-dlp` should also be available in your system PATH for using the `-list` flag. If the package is installed in a virtual environment (venv) or isolated using tools like `uv`, ensure yt-dlp added to your system env PATH.*
|
|
72
70
|
|
|
73
71
|
## 📦 Installation
|
|
74
72
|
|
|
@@ -94,13 +92,11 @@ usage: ytfetch URL ... [OPTIONS] [yt-dlp OPTIONS]
|
|
|
94
92
|
|
|
95
93
|
options:
|
|
96
94
|
-h Show this help message and exit
|
|
97
|
-
|
|
95
|
+
--help Show yt-dlp help message and exit
|
|
98
96
|
-avcmp3 Download video in AVC (h.264) & audio in mp3 format
|
|
99
97
|
-q QUALITY Video quality (e.g., 1080, 720). Default is 1080
|
|
100
98
|
-mp3 Extract audio only as MP3
|
|
101
99
|
-audio Extract audio only
|
|
102
|
-
-fetch RANGE [RANGE ...] Alternate for --playlist-items. Single value applies globally
|
|
103
|
-
Multiple value must match number of playlist URLs provided
|
|
104
100
|
-list [NAME] List playlist contents. Accepts values for searching across playlist
|
|
105
101
|
```
|
|
106
102
|
|
|
@@ -126,17 +122,6 @@ Extract Audio Only in Mp3
|
|
|
126
122
|
```bash
|
|
127
123
|
ytfetch "VIDEO_URL" -mp3
|
|
128
124
|
```
|
|
129
|
-
#### Smart Fetching
|
|
130
|
-
|
|
131
|
-
Global Limit
|
|
132
|
-
```bash
|
|
133
|
-
ytfetch "PLAYLIST_URL_1" "PLAYLIST_URL_2" -fetch "1-10"
|
|
134
|
-
```
|
|
135
|
-
Mapped Ranges
|
|
136
|
-
```bash
|
|
137
|
-
ytfetch "PLAYLIST_A" "PLAYLIST_B" -fetch "1-5" "10-15"
|
|
138
|
-
```
|
|
139
|
-
*Note: The number of fetch arguments must match the number of URLs*
|
|
140
125
|
|
|
141
126
|
#### Listing and Searching
|
|
142
127
|
|
|
@@ -148,10 +133,6 @@ Search through a playlist
|
|
|
148
133
|
```bash
|
|
149
134
|
ytfetch "PLAYLIST_URL" -list "remix"
|
|
150
135
|
```
|
|
151
|
-
Combined Fetch & Search
|
|
152
|
-
```bash
|
|
153
|
-
ytfetch "PLAYLIST_URL" -fetch ":50" -list "search_word"
|
|
154
|
-
```
|
|
155
136
|
|
|
156
137
|
#### Passthrough Options
|
|
157
138
|
|
|
@@ -169,6 +150,11 @@ Limit Speed
|
|
|
169
150
|
```bash
|
|
170
151
|
ytfetch -r "500K" "VIDEO_URL"
|
|
171
152
|
```
|
|
153
|
+
Selected range Search
|
|
154
|
+
```bash
|
|
155
|
+
ytfetch "PLAYLIST_URL" -I ":50" -list "search_word"
|
|
156
|
+
```
|
|
157
|
+
*and so on...*
|
|
172
158
|
|
|
173
159
|
## 🔖 License
|
|
174
160
|
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
# ytfetch
|
|
2
2
|
|
|
3
|
-
**ytfetch** simplifies media downloading and playlist management by adding
|
|
3
|
+
**ytfetch** simplifies media downloading and playlist management by adding playlist searching, and streamlined format handling on top of the powerful [yt-dlp](https://github.com/yt-dlp/yt-dlp) engine.
|
|
4
4
|
|
|
5
5
|
## ✨ Features
|
|
6
6
|
|
|
7
|
-
* **Smart Fetching:** Apply limits to multiple playlists or map specific ranges in a single download.
|
|
8
7
|
* **Search:** Use `-list` to display or search across single or multiple playlists contents.
|
|
9
8
|
* **Direct Download:** Single flag to download in legacy `avc/h.264` and `mp3` format and audio only extraction.
|
|
10
9
|
* **Video Quality:** Mention preffered video quality directly.
|
|
@@ -21,7 +20,6 @@ Download builds from [here](https://ffmpeg.org/download.html)
|
|
|
21
20
|
Check this [guide](https://github.com/yt-dlp/yt-dlp/wiki/EJS) for more info.
|
|
22
21
|
Also check [yt-dlp dependency](https://github.com/yt-dlp/yt-dlp#dependencies) for optional dependencies.
|
|
23
22
|
|
|
24
|
-
*Note: `yt-dlp` should also be available in your system PATH for using the `-list` flag. If the package is installed in a virtual environment (venv) or isolated using tools like `uv`, ensure yt-dlp added to your system env PATH.*
|
|
25
23
|
|
|
26
24
|
## 📦 Installation
|
|
27
25
|
|
|
@@ -33,8 +31,8 @@ pip install ytfetch
|
|
|
33
31
|
|
|
34
32
|
### via source
|
|
35
33
|
```bash
|
|
36
|
-
git clone
|
|
37
|
-
cd
|
|
34
|
+
git clone https://github.com/voidsnax/ytFetch.git
|
|
35
|
+
cd ytFetch
|
|
38
36
|
pip install -e .
|
|
39
37
|
```
|
|
40
38
|
|
|
@@ -47,14 +45,11 @@ usage: ytfetch URL ... [OPTIONS] [yt-dlp OPTIONS]
|
|
|
47
45
|
|
|
48
46
|
options:
|
|
49
47
|
-h Show this help message and exit
|
|
50
|
-
|
|
48
|
+
--help Show yt-dlp help messag and exit
|
|
51
49
|
-avcmp3 Download video in AVC (h.264) & audio in mp3 format
|
|
52
50
|
-q QUALITY Video quality (e.g., 1080, 720). Default is 1080
|
|
53
51
|
-mp3 Extract audio only as MP3
|
|
54
|
-
-audio Extract audio
|
|
55
|
-
-fetch RANGE [RANGE ...] Alternate for --playlist-items. Single value applies globally
|
|
56
|
-
Multiple value must number of playlist URLs provided
|
|
57
|
-
match number of playlist URLs provided
|
|
52
|
+
-audio Extract best audio
|
|
58
53
|
-list [NAME] List playlist contents. Accepts values for searching across playlist
|
|
59
54
|
```
|
|
60
55
|
|
|
@@ -80,17 +75,6 @@ Extract Audio Only in Mp3
|
|
|
80
75
|
```bash
|
|
81
76
|
ytfetch "VIDEO_URL" -mp3
|
|
82
77
|
```
|
|
83
|
-
#### Smart Fetching
|
|
84
|
-
|
|
85
|
-
Global Limit
|
|
86
|
-
```bash
|
|
87
|
-
ytfetch "PLAYLIST_URL_1" "PLAYLIST_URL_2" -fetch "1-10"
|
|
88
|
-
```
|
|
89
|
-
Mapped Ranges
|
|
90
|
-
```bash
|
|
91
|
-
ytfetch "PLAYLIST_A" "PLAYLIST_B" -fetch "1-5" "10-15"
|
|
92
|
-
```
|
|
93
|
-
*Note: The number of fetch arguments must match the number of URLs*
|
|
94
78
|
|
|
95
79
|
#### Listing and Searching
|
|
96
80
|
|
|
@@ -102,10 +86,6 @@ Search through a playlist
|
|
|
102
86
|
```bash
|
|
103
87
|
ytfetch "PLAYLIST_URL" -list "remix"
|
|
104
88
|
```
|
|
105
|
-
Combined Fetch & Search
|
|
106
|
-
```bash
|
|
107
|
-
ytfetch "PLAYLIST_URL" -fetch ":50" -list "search_word"
|
|
108
|
-
```
|
|
109
89
|
|
|
110
90
|
#### Passthrough Options
|
|
111
91
|
|
|
@@ -123,6 +103,11 @@ Limit Speed
|
|
|
123
103
|
```bash
|
|
124
104
|
ytfetch -r "500K" "VIDEO_URL"
|
|
125
105
|
```
|
|
106
|
+
Selected range Search
|
|
107
|
+
```bash
|
|
108
|
+
ytfetch "PLAYLIST_URL" -I ":50" -list "search_word"
|
|
109
|
+
```
|
|
110
|
+
*and so on...*
|
|
126
111
|
|
|
127
112
|
## 🔖 License
|
|
128
113
|
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
# ytfetch
|
|
2
2
|
|
|
3
|
-
**ytfetch** simplifies media downloading and playlist management by adding
|
|
3
|
+
**ytfetch** simplifies media downloading and playlist management by adding playlist searching, and streamlined format handling on top of the powerful [yt-dlp](https://github.com/yt-dlp/yt-dlp) engine.
|
|
4
4
|
|
|
5
5
|
## ✨ Features
|
|
6
6
|
|
|
7
|
-
* **Smart Fetching:** Apply limits to multiple playlists or map specific ranges in a single download.
|
|
8
7
|
* **Search:** Use `-list` to display or search across single or multiple playlists contents.
|
|
9
8
|
* **Direct Download:** Single flag to download in legacy `avc/h.264` and `mp3` format and audio only extraction.
|
|
10
9
|
* **Video Quality:** Mention preffered video quality directly.
|
|
@@ -54,7 +53,6 @@ Download builds from [here](https://ffmpeg.org/download.html)
|
|
|
54
53
|
Check this [guide](https://github.com/yt-dlp/yt-dlp/wiki/EJS) for more info.
|
|
55
54
|
Also check [yt-dlp dependency](https://github.com/yt-dlp/yt-dlp#dependencies) for optional dependencies.
|
|
56
55
|
|
|
57
|
-
*Note: `yt-dlp` should also be available in your system PATH for using the `-list` flag. If the package is installed in a virtual environment (venv) or isolated using tools like `uv`, ensure yt-dlp added to your system env PATH.*
|
|
58
56
|
|
|
59
57
|
## 📦 Installation
|
|
60
58
|
|
|
@@ -80,13 +78,11 @@ usage: ytfetch URL ... [OPTIONS] [yt-dlp OPTIONS]
|
|
|
80
78
|
|
|
81
79
|
options:
|
|
82
80
|
-h Show this help message and exit
|
|
83
|
-
|
|
81
|
+
--help Show yt-dlp help message and exit
|
|
84
82
|
-avcmp3 Download video in AVC (h.264) & audio in mp3 format
|
|
85
83
|
-q QUALITY Video quality (e.g., 1080, 720). Default is 1080
|
|
86
84
|
-mp3 Extract audio only as MP3
|
|
87
85
|
-audio Extract audio only
|
|
88
|
-
-fetch RANGE [RANGE ...] Alternate for --playlist-items. Single value applies globally
|
|
89
|
-
Multiple value must match number of playlist URLs provided
|
|
90
86
|
-list [NAME] List playlist contents. Accepts values for searching across playlist
|
|
91
87
|
```
|
|
92
88
|
|
|
@@ -112,17 +108,6 @@ Extract Audio Only in Mp3
|
|
|
112
108
|
```bash
|
|
113
109
|
ytfetch "VIDEO_URL" -mp3
|
|
114
110
|
```
|
|
115
|
-
#### Smart Fetching
|
|
116
|
-
|
|
117
|
-
Global Limit
|
|
118
|
-
```bash
|
|
119
|
-
ytfetch "PLAYLIST_URL_1" "PLAYLIST_URL_2" -fetch "1-10"
|
|
120
|
-
```
|
|
121
|
-
Mapped Ranges
|
|
122
|
-
```bash
|
|
123
|
-
ytfetch "PLAYLIST_A" "PLAYLIST_B" -fetch "1-5" "10-15"
|
|
124
|
-
```
|
|
125
|
-
*Note: The number of fetch arguments must match the number of URLs*
|
|
126
111
|
|
|
127
112
|
#### Listing and Searching
|
|
128
113
|
|
|
@@ -134,10 +119,6 @@ Search through a playlist
|
|
|
134
119
|
```bash
|
|
135
120
|
ytfetch "PLAYLIST_URL" -list "remix"
|
|
136
121
|
```
|
|
137
|
-
Combined Fetch & Search
|
|
138
|
-
```bash
|
|
139
|
-
ytfetch "PLAYLIST_URL" -fetch ":50" -list "search_word"
|
|
140
|
-
```
|
|
141
122
|
|
|
142
123
|
#### Passthrough Options
|
|
143
124
|
|
|
@@ -155,6 +136,11 @@ Limit Speed
|
|
|
155
136
|
```bash
|
|
156
137
|
ytfetch -r "500K" "VIDEO_URL"
|
|
157
138
|
```
|
|
139
|
+
Selected range Search
|
|
140
|
+
```bash
|
|
141
|
+
ytfetch "PLAYLIST_URL" -I ":50" -list "search_word"
|
|
142
|
+
```
|
|
143
|
+
*and so on...*
|
|
158
144
|
|
|
159
145
|
## 🔖 License
|
|
160
146
|
|
|
@@ -1,30 +1,29 @@
|
|
|
1
1
|
import argparse
|
|
2
2
|
|
|
3
|
-
|
|
4
3
|
def parse_arguments():
|
|
5
4
|
parser = argparse.ArgumentParser(
|
|
6
|
-
usage = "ytfetch URL ... [OPTIONS] [yt-dlp OPTIONS]
|
|
7
|
-
description="ytfetch: yt-dlp wrapper with flexible args.",
|
|
8
|
-
epilog = "
|
|
9
|
-
"Doc & issues: https://github.com/voidsnax/ytFetch",
|
|
5
|
+
usage = "ytfetch URL ... [OPTIONS] [yt-dlp OPTIONS]",
|
|
6
|
+
description="ytfetch: yt-dlp wrapper with some flexible args.",
|
|
7
|
+
epilog = "Doc & issues: https://github.com/voidsnax/ytFetch",
|
|
10
8
|
formatter_class=AlignedHelpFormatter,
|
|
11
|
-
allow_abbrev=False
|
|
9
|
+
allow_abbrev=False,
|
|
10
|
+
add_help=False
|
|
12
11
|
)
|
|
13
12
|
|
|
14
|
-
parser.add_argument("
|
|
15
|
-
parser.add_argument("
|
|
16
|
-
help="Download video in AVC (h.264) & audio in mp3 format")
|
|
13
|
+
parser.add_argument("-h", action="help", help="Show this help message and exit")
|
|
14
|
+
parser.add_argument("--help", action="store_true", help="Show yt-dlp help message and exit")
|
|
17
15
|
parser.add_argument("-q", metavar="QUALITY", nargs="?", default="default",
|
|
18
|
-
help="Video quality (e.g., 1080, 720). Default will pick best video"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
parser.add_argument("-
|
|
24
|
-
|
|
25
|
-
|
|
16
|
+
help="Video quality (e.g., 1080, 720). Default will pick best video"
|
|
17
|
+
)
|
|
18
|
+
parser.add_argument("-avcmp3", action="store_true",
|
|
19
|
+
help="Download video in AVC (h.264) & audio in mp3 format"
|
|
20
|
+
)
|
|
21
|
+
parser.add_argument("-mp3", action="store_true", help="Extract audio only as MP3")
|
|
22
|
+
parser.add_argument("-audio", action="store_true", help="Extract audio only (bestaudio)")
|
|
23
|
+
|
|
26
24
|
parser.add_argument("-list", metavar="NAME", nargs="?", const="default",
|
|
27
|
-
help="List playlist contents. Accepts values for searching across playlist"
|
|
25
|
+
help="List playlist contents. Accepts values for searching across playlist"
|
|
26
|
+
)
|
|
28
27
|
|
|
29
28
|
args, unknown_args = parser.parse_known_args()
|
|
30
29
|
return args, unknown_args
|
|
@@ -4,7 +4,6 @@ from .utils import is_android
|
|
|
4
4
|
|
|
5
5
|
init(autoreset=True)
|
|
6
6
|
|
|
7
|
-
|
|
8
7
|
# --- Custom Loggers ---
|
|
9
8
|
class YTFetchLogger:
|
|
10
9
|
def debug(self, msg):
|
|
@@ -24,7 +23,13 @@ class YTFetchLogger:
|
|
|
24
23
|
print(f"\n{clean_msg}")
|
|
25
24
|
|
|
26
25
|
if "Merging formats" in msg:
|
|
27
|
-
print(msg
|
|
26
|
+
print(msg)
|
|
27
|
+
|
|
28
|
+
if "Adding metadata" in msg:
|
|
29
|
+
print(msg)
|
|
30
|
+
|
|
31
|
+
if "Adding thumbnail" in msg:
|
|
32
|
+
print(msg)
|
|
28
33
|
|
|
29
34
|
def info(self, msg):
|
|
30
35
|
pass
|
|
@@ -37,8 +42,13 @@ class YTFetchLogger:
|
|
|
37
42
|
def error(self, msg):
|
|
38
43
|
print(f"\n{msg}")
|
|
39
44
|
|
|
40
|
-
class
|
|
41
|
-
def debug(self, msg):
|
|
45
|
+
class ListLogger:
|
|
46
|
+
def debug(self, msg):
|
|
47
|
+
if "Downloading playlist:" in msg:
|
|
48
|
+
playlist_title = msg.replace("[download] Downloading playlist: ", "")
|
|
49
|
+
playlist_string = f"▶ Playlist: {playlist_title}"
|
|
50
|
+
print(f"▶ Playlist: {Fore.CYAN}{playlist_title}{Fore.RESET}")
|
|
51
|
+
print("-" * len(playlist_string))
|
|
42
52
|
def info(self, msg): pass
|
|
43
53
|
def warning(self, msg): pass
|
|
44
54
|
def error(self, msg): print(msg)
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import signal
|
|
3
|
+
|
|
4
|
+
signal.signal(signal.SIGINT, lambda x, y: sys.exit(0))
|
|
5
|
+
|
|
6
|
+
import yt_dlp
|
|
7
|
+
from colorama import Fore
|
|
8
|
+
from urllib.parse import urlsplit
|
|
9
|
+
|
|
10
|
+
from .cli import parse_arguments
|
|
11
|
+
from .utils import (
|
|
12
|
+
format_selector,
|
|
13
|
+
print_error
|
|
14
|
+
)
|
|
15
|
+
from .formatter import (
|
|
16
|
+
ListLogger,
|
|
17
|
+
YTFetchLogger,
|
|
18
|
+
progress_hook
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
signal.signal(signal.SIGINT, signal.default_int_handler)
|
|
22
|
+
|
|
23
|
+
def process_urls(cli_args, extra_ydl_args):
|
|
24
|
+
|
|
25
|
+
if cli_args.help:
|
|
26
|
+
yt_dlp.parse_options(["-h"])
|
|
27
|
+
|
|
28
|
+
ydl_args = []
|
|
29
|
+
|
|
30
|
+
# --- List ---
|
|
31
|
+
if cli_args.list:
|
|
32
|
+
ydl_args.extend(["--flat-playlist", "--print", "%(playlist_index)s - %(title)s"])
|
|
33
|
+
|
|
34
|
+
if cli_args.list != 'default':
|
|
35
|
+
list_val = cli_args.list
|
|
36
|
+
parsed = urlsplit(list_val) # to skip urls
|
|
37
|
+
if all([parsed.scheme, parsed.netloc]):
|
|
38
|
+
extra_ydl_args.append(list_val)
|
|
39
|
+
else:
|
|
40
|
+
ydl_args.extend(["--match-filters", f"title ~= '(?i){list_val}'"])
|
|
41
|
+
|
|
42
|
+
else:
|
|
43
|
+
# --- format selection ---
|
|
44
|
+
ydl_args.extend(["--format", format_selector(cli_args)])
|
|
45
|
+
|
|
46
|
+
# --- audio quality ---
|
|
47
|
+
if cli_args.q == 'default':
|
|
48
|
+
a_quality = '2'
|
|
49
|
+
else:
|
|
50
|
+
a_quality = cli_args.q
|
|
51
|
+
|
|
52
|
+
# --- mp3 ---
|
|
53
|
+
if cli_args.mp3:
|
|
54
|
+
ydl_args.extend(["-x", "--audio-format", "mp3", "--audio-quality", a_quality])
|
|
55
|
+
|
|
56
|
+
# --- audio ---
|
|
57
|
+
elif cli_args.audio:
|
|
58
|
+
ydl_args.extend(["-x", "--audio-quality", a_quality])
|
|
59
|
+
|
|
60
|
+
# --- avcmp3 ---
|
|
61
|
+
elif cli_args.avcmp3:
|
|
62
|
+
ydl_args.extend(["--merge-output-format", "mp4", "--postprocessor-args", "ffmpeg:-c:v copy -c:a libmp3lame -b:a 192k"])
|
|
63
|
+
|
|
64
|
+
# --- default output template ---
|
|
65
|
+
ydl_args.extend(["-o", "%(title)s.%(ext)s"])
|
|
66
|
+
|
|
67
|
+
# --- final parsing ---
|
|
68
|
+
ydl_args.extend(extra_ydl_args)
|
|
69
|
+
_, _, urls, ydl_opts = yt_dlp.parse_options(ydl_args)
|
|
70
|
+
|
|
71
|
+
if not urls:
|
|
72
|
+
print_error("Provide atleast one URL...")
|
|
73
|
+
return
|
|
74
|
+
|
|
75
|
+
# --- output template aliases ---
|
|
76
|
+
if ydl_opts.get('outtmpl', {}).get('default') in ['autonumber', 'num']: # type: ignore
|
|
77
|
+
ydl_opts["outtmpl"] = {'default': '%(playlist_autonumber)s - %(title)s.%(ext)s'}
|
|
78
|
+
|
|
79
|
+
# --- logger ---
|
|
80
|
+
if not cli_args.list: ydl_opts['progress_hooks'] = [progress_hook]
|
|
81
|
+
if not ydl_opts.get('verbose'):
|
|
82
|
+
ydl_opts['logger'] = ListLogger() if cli_args.list else YTFetchLogger() # type: ignore
|
|
83
|
+
|
|
84
|
+
# --- download ---
|
|
85
|
+
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
|
86
|
+
for url in urls:
|
|
87
|
+
ydl.download([url])
|
|
88
|
+
|
|
89
|
+
def main():
|
|
90
|
+
try:
|
|
91
|
+
args, extra_args = parse_arguments()
|
|
92
|
+
process_urls(args, extra_args)
|
|
93
|
+
except KeyboardInterrupt:
|
|
94
|
+
print(f"\n\n{Fore.YELLOW}Process aborted by user{Fore.RESET}")
|
|
95
|
+
sys.exit(1)
|
|
96
|
+
except Exception as e:
|
|
97
|
+
print_error(f"Unexpected error: {e}")
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from colorama import Fore, init
|
|
3
|
+
|
|
4
|
+
init(autoreset=True)
|
|
5
|
+
|
|
6
|
+
def format_selector(args):
|
|
7
|
+
height = args.q.rstrip("p")
|
|
8
|
+
|
|
9
|
+
if args.mp3 or args.audio:
|
|
10
|
+
return "bestaudio/best"
|
|
11
|
+
|
|
12
|
+
if args.avcmp3:
|
|
13
|
+
if height != "default":
|
|
14
|
+
return f"bestvideo[vcodec^=avc1][height<={height}]+bestaudio/best[vcodec^=avc1][height<={height}]"
|
|
15
|
+
|
|
16
|
+
return f"bestvideo[vcodec^=avc1]+bestaudio/best[vcodec^=avc1]"
|
|
17
|
+
|
|
18
|
+
if height != "default":
|
|
19
|
+
return f"bestvideo[height<={height}]+bestaudio/best[height<={height}]"
|
|
20
|
+
|
|
21
|
+
return f"bestvideo+bestaudio/best"
|
|
22
|
+
|
|
23
|
+
def print_error(msg):
|
|
24
|
+
print(f"{Fore.RED}{msg}")
|
|
25
|
+
|
|
26
|
+
def is_android():
|
|
27
|
+
return hasattr(sys, 'getandroidapilevel')
|
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
import sys
|
|
2
|
-
import signal
|
|
3
|
-
|
|
4
|
-
signal.signal(signal.SIGINT, lambda x, y: sys.exit(0))
|
|
5
|
-
|
|
6
|
-
import subprocess
|
|
7
|
-
import yt_dlp
|
|
8
|
-
from colorama import Fore
|
|
9
|
-
|
|
10
|
-
from .cli import parse_arguments
|
|
11
|
-
from .utils import (
|
|
12
|
-
parse_passthrough_args,
|
|
13
|
-
validate_fetch_ranges,
|
|
14
|
-
format_selector,
|
|
15
|
-
print_error,
|
|
16
|
-
is_ytdlp_available
|
|
17
|
-
)
|
|
18
|
-
from .formatter import (
|
|
19
|
-
YTFetchLogger,
|
|
20
|
-
ErrorOnlyLogger,
|
|
21
|
-
progress_hook
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
signal.signal(signal.SIGINT, signal.default_int_handler)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def process_urls(custom_args, raw_ytdlp_args):
|
|
28
|
-
urls = custom_args.url
|
|
29
|
-
passthrough_opts = parse_passthrough_args(raw_ytdlp_args)
|
|
30
|
-
|
|
31
|
-
ydl_opts = {
|
|
32
|
-
'outtmpl': '%(title)s.%(ext)s',
|
|
33
|
-
'logger': YTFetchLogger(),
|
|
34
|
-
'progress_hooks': [progress_hook],
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
ydl_opts.update(passthrough_opts)
|
|
38
|
-
|
|
39
|
-
# --- List ---
|
|
40
|
-
if not is_ytdlp_available():
|
|
41
|
-
print_error("yt-dlp cant be accesed via terminal, Make sure its available in system PATH")
|
|
42
|
-
return
|
|
43
|
-
if custom_args.list:
|
|
44
|
-
base_cmd = [
|
|
45
|
-
"yt-dlp",
|
|
46
|
-
"--flat-playlist",
|
|
47
|
-
"--print", "%(playlist_index)s - %(title)s"
|
|
48
|
-
]
|
|
49
|
-
|
|
50
|
-
passthrough_args = [arg for arg in raw_ytdlp_args if arg not in urls]
|
|
51
|
-
base_cmd.extend(passthrough_args)
|
|
52
|
-
command = []
|
|
53
|
-
|
|
54
|
-
ydl_opts['extract_flat'] = 'in_playlist'
|
|
55
|
-
ydl_opts['logger'] = ErrorOnlyLogger()
|
|
56
|
-
|
|
57
|
-
search_mode = False
|
|
58
|
-
|
|
59
|
-
fetch_ranges = custom_args.fetch
|
|
60
|
-
if fetch_ranges:
|
|
61
|
-
if len(fetch_ranges) >1:
|
|
62
|
-
validate_fetch_ranges(fetch_ranges,urls)
|
|
63
|
-
for fetch_range in fetch_ranges:
|
|
64
|
-
command.append(base_cmd + [ "--playlist-items", fetch_range])
|
|
65
|
-
else:
|
|
66
|
-
command.append(base_cmd + ["--playlist-items", fetch_ranges[0]])
|
|
67
|
-
else:
|
|
68
|
-
command.append(base_cmd)
|
|
69
|
-
|
|
70
|
-
# make sure command is a list of list
|
|
71
|
-
if len(command) == 1 and len(urls) > 1:
|
|
72
|
-
command = [cmd.copy() for cmd in [command[0]] * len(urls)]
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if custom_args.list != 'default':
|
|
76
|
-
search_mode = True
|
|
77
|
-
list_val = custom_args.list
|
|
78
|
-
search_pattern = list_val.lower()
|
|
79
|
-
for url, cmd in zip(urls, command):
|
|
80
|
-
# currently for title fetching this is more reliable
|
|
81
|
-
with yt_dlp.YoutubeDL(ydl_opts) as ydl: #type: ignore
|
|
82
|
-
try:
|
|
83
|
-
info = ydl.extract_info(url, download=False)
|
|
84
|
-
if 'entries' not in info:
|
|
85
|
-
print(f"Single Video: {info.get('title')}")
|
|
86
|
-
else:
|
|
87
|
-
title = f"▶ Playlist: {info.get('title')}"
|
|
88
|
-
print(f"▶ Playlist: {Fore.CYAN}{info.get('title')}{Fore.RESET}")
|
|
89
|
-
print(f"{"-" * len(title)}")
|
|
90
|
-
except Exception as e:
|
|
91
|
-
print(f"Error getting {url} title: {e}")
|
|
92
|
-
try:
|
|
93
|
-
cmd.append(url)
|
|
94
|
-
# print(cmd)
|
|
95
|
-
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
96
|
-
found_match = False
|
|
97
|
-
for line in result.stdout.splitlines():
|
|
98
|
-
idx, title = line.split(" - ", 1)
|
|
99
|
-
video = f"{Fore.YELLOW}{idx}{Fore.RESET} - {title}"
|
|
100
|
-
if search_mode:
|
|
101
|
-
if search_pattern in line.lower():
|
|
102
|
-
found_match = True
|
|
103
|
-
print(video)
|
|
104
|
-
else:
|
|
105
|
-
print(video)
|
|
106
|
-
if not found_match and search_mode:
|
|
107
|
-
print(f"No match found for {Fore.YELLOW}{search_pattern}{Fore.RESET}")
|
|
108
|
-
except Exception as e:
|
|
109
|
-
print_error(f"Error listing {url}: {e}")
|
|
110
|
-
|
|
111
|
-
return
|
|
112
|
-
|
|
113
|
-
# --- format selection ---
|
|
114
|
-
ydl_opts['format'] = format_selector(custom_args)
|
|
115
|
-
|
|
116
|
-
if custom_args.mp3:
|
|
117
|
-
ydl_opts['postprocessors'] = [{
|
|
118
|
-
'key': 'FFmpegExtractAudio',
|
|
119
|
-
'preferredcodec': 'mp3',
|
|
120
|
-
'preferredquality': '192',
|
|
121
|
-
}]
|
|
122
|
-
|
|
123
|
-
elif custom_args.avcmp3:
|
|
124
|
-
ydl_opts['merge_output_format'] = 'mp4'
|
|
125
|
-
ydl_opts['postprocessor_args'] = {
|
|
126
|
-
'ffmpeg': ['-c:v', 'copy', '-c:a', 'libmp3lame', '-b:a', '192k']
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
# --- Download ---
|
|
130
|
-
def run_download(url, playlist_items=None):
|
|
131
|
-
opts = ydl_opts.copy()
|
|
132
|
-
if playlist_items:
|
|
133
|
-
opts['playlist_items'] = playlist_items
|
|
134
|
-
with yt_dlp.YoutubeDL(opts) as ydl: #type:ignore
|
|
135
|
-
try:
|
|
136
|
-
ydl.download([url])
|
|
137
|
-
except Exception as e:
|
|
138
|
-
# print(f"Download error for {url}: {e}")
|
|
139
|
-
pass
|
|
140
|
-
|
|
141
|
-
fetch_ranges = custom_args.fetch
|
|
142
|
-
if fetch_ranges:
|
|
143
|
-
if len(fetch_ranges) == 1:
|
|
144
|
-
for url in urls:
|
|
145
|
-
run_download(url, fetch_ranges[0])
|
|
146
|
-
else:
|
|
147
|
-
validate_fetch_ranges(fetch_ranges,urls)
|
|
148
|
-
for url, rng in zip(urls, fetch_ranges):
|
|
149
|
-
run_download(url, rng)
|
|
150
|
-
else:
|
|
151
|
-
for url in urls:
|
|
152
|
-
run_download(url)
|
|
153
|
-
|
|
154
|
-
def main():
|
|
155
|
-
try:
|
|
156
|
-
custom_args, ytdlp_args = parse_arguments()
|
|
157
|
-
process_urls(custom_args, ytdlp_args)
|
|
158
|
-
except KeyboardInterrupt:
|
|
159
|
-
print(f"\n\n{Fore.YELLOW}Process aborted by user{Fore.RESET}")
|
|
160
|
-
sys.exit(1)
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import shutil
|
|
2
|
-
import sys
|
|
3
|
-
from colorama import Fore, init
|
|
4
|
-
from yt_dlp.options import create_parser
|
|
5
|
-
|
|
6
|
-
init(autoreset=True)
|
|
7
|
-
|
|
8
|
-
def parse_passthrough_args(args_list):
|
|
9
|
-
"""
|
|
10
|
-
Parses unknown arguments using yt-dlp's internal parser.
|
|
11
|
-
Returns a dictionary containing ONLY the options explicitly passed by the user.
|
|
12
|
-
"""
|
|
13
|
-
parser = create_parser()
|
|
14
|
-
try:
|
|
15
|
-
parsed, _ = parser.parse_known_args(args_list)
|
|
16
|
-
except Exception as e:
|
|
17
|
-
print(e,end='')
|
|
18
|
-
sys.exit(1)
|
|
19
|
-
|
|
20
|
-
opts = vars(parsed) # convert to dict
|
|
21
|
-
|
|
22
|
-
# Filter out defaults to return ONLY user-provided args
|
|
23
|
-
# create a baseline parser to see what the defaults are
|
|
24
|
-
base_parser = create_parser()
|
|
25
|
-
base_parsed, _ = base_parser.parse_args([])
|
|
26
|
-
base_opts = vars(base_parsed)
|
|
27
|
-
|
|
28
|
-
# Build dictionary of only changed items
|
|
29
|
-
final_opts = {}
|
|
30
|
-
for key, value in opts.items():
|
|
31
|
-
# Only keep the option if the user changed it from the default
|
|
32
|
-
if base_opts[key] != value:
|
|
33
|
-
final_opts[key] = value
|
|
34
|
-
|
|
35
|
-
return final_opts
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def validate_fetch_ranges(fetch_ranges, urls):
|
|
39
|
-
if len(fetch_ranges) != len(urls):
|
|
40
|
-
print_error(f"Error: Provided {len(fetch_ranges)} fetch ranges for {len(urls)} URLs.")
|
|
41
|
-
sys.exit(1)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def format_selector(args):
|
|
45
|
-
height = args.q.rstrip("p")
|
|
46
|
-
|
|
47
|
-
if args.mp3 or args.audio:
|
|
48
|
-
return "bestaudio/best"
|
|
49
|
-
|
|
50
|
-
if args.avcmp3:
|
|
51
|
-
if height != "default":
|
|
52
|
-
return f"bestvideo[vcodec^=avc1][height<={height}]+bestaudio/best[vcodec^=avc1][height<={height}]"
|
|
53
|
-
|
|
54
|
-
return f"bestvideo[vcodec^=avc1]+bestaudio/best[vcodec^=avc1]"
|
|
55
|
-
|
|
56
|
-
if height != "default":
|
|
57
|
-
return f"bestvideo[height<={height}]+bestaudio/best[height<={height}]"
|
|
58
|
-
|
|
59
|
-
return f"bestvideo+bestaudio/best"
|
|
60
|
-
|
|
61
|
-
def print_error(msg):
|
|
62
|
-
print(f"{Fore.RED}{msg}")
|
|
63
|
-
|
|
64
|
-
def is_android():
|
|
65
|
-
return hasattr(sys, 'getandroidapilevel')
|
|
66
|
-
|
|
67
|
-
def is_ytdlp_available():
|
|
68
|
-
"""Return True if yt-dlp is available in the system PATH."""
|
|
69
|
-
return shutil.which("yt-dlp") is not None
|
|
70
|
-
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|