ytfetch 1.2.0__tar.gz → 2.1.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.1.0}/PKG-INFO +14 -23
- {ytfetch-1.2.0 → ytfetch-2.1.0}/README-pypi.md +16 -26
- {ytfetch-1.2.0 → ytfetch-2.1.0}/README.md +13 -22
- {ytfetch-1.2.0 → ytfetch-2.1.0}/pyproject.toml +1 -1
- ytfetch-2.1.0/src/ytfetch/cli.py +48 -0
- {ytfetch-1.2.0 → ytfetch-2.1.0}/src/ytfetch/formatter.py +14 -4
- ytfetch-2.1.0/src/ytfetch/main.py +100 -0
- ytfetch-2.1.0/src/ytfetch/utils.py +27 -0
- ytfetch-1.2.0/src/ytfetch/cli.py +0 -44
- 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.1.0}/.gitattributes +0 -0
- {ytfetch-1.2.0 → ytfetch-2.1.0}/.github/workflows/publish.yaml +0 -0
- {ytfetch-1.2.0 → ytfetch-2.1.0}/.gitignore +0 -0
- {ytfetch-1.2.0 → ytfetch-2.1.0}/LICENSE +0 -0
- {ytfetch-1.2.0 → ytfetch-2.1.0}/src/ytfetch/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ytfetch
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 2.1.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
|
|
@@ -12,13 +12,12 @@ Requires-Dist: colorama
|
|
|
12
12
|
Requires-Dist: yt-dlp[curl-cffi,default]
|
|
13
13
|
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,14 +92,17 @@ 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
|
|
101
|
+
|
|
102
|
+
output template aliases:
|
|
103
|
+
-o autonumber/num -> playlist-numbered naming
|
|
104
|
+
-o music/song -> title + artist naming
|
|
105
|
+
-o default -> default naming
|
|
105
106
|
```
|
|
106
107
|
|
|
107
108
|
### Examples
|
|
@@ -126,17 +127,6 @@ Extract Audio Only in Mp3
|
|
|
126
127
|
```bash
|
|
127
128
|
ytfetch "VIDEO_URL" -mp3
|
|
128
129
|
```
|
|
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
130
|
|
|
141
131
|
#### Listing and Searching
|
|
142
132
|
|
|
@@ -148,10 +138,6 @@ Search through a playlist
|
|
|
148
138
|
```bash
|
|
149
139
|
ytfetch "PLAYLIST_URL" -list "remix"
|
|
150
140
|
```
|
|
151
|
-
Combined Fetch & Search
|
|
152
|
-
```bash
|
|
153
|
-
ytfetch "PLAYLIST_URL" -fetch ":50" -list "search_word"
|
|
154
|
-
```
|
|
155
141
|
|
|
156
142
|
#### Passthrough Options
|
|
157
143
|
|
|
@@ -169,6 +155,11 @@ Limit Speed
|
|
|
169
155
|
```bash
|
|
170
156
|
ytfetch -r "500K" "VIDEO_URL"
|
|
171
157
|
```
|
|
158
|
+
Selected range Search
|
|
159
|
+
```bash
|
|
160
|
+
ytfetch "PLAYLIST_URL" -I ":50" -list "search_word"
|
|
161
|
+
```
|
|
162
|
+
*and so on...*
|
|
172
163
|
|
|
173
164
|
## 🔖 License
|
|
174
165
|
|
|
@@ -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,15 +45,17 @@ 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
|
|
54
|
+
|
|
55
|
+
output template aliases:
|
|
56
|
+
-o autonumber/num -> playlist-numbered naming
|
|
57
|
+
-o music/song -> title + artist naming
|
|
58
|
+
-o default -> default naming
|
|
59
59
|
```
|
|
60
60
|
|
|
61
61
|
### Examples
|
|
@@ -80,17 +80,6 @@ Extract Audio Only in Mp3
|
|
|
80
80
|
```bash
|
|
81
81
|
ytfetch "VIDEO_URL" -mp3
|
|
82
82
|
```
|
|
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
83
|
|
|
95
84
|
#### Listing and Searching
|
|
96
85
|
|
|
@@ -102,10 +91,6 @@ Search through a playlist
|
|
|
102
91
|
```bash
|
|
103
92
|
ytfetch "PLAYLIST_URL" -list "remix"
|
|
104
93
|
```
|
|
105
|
-
Combined Fetch & Search
|
|
106
|
-
```bash
|
|
107
|
-
ytfetch "PLAYLIST_URL" -fetch ":50" -list "search_word"
|
|
108
|
-
```
|
|
109
94
|
|
|
110
95
|
#### Passthrough Options
|
|
111
96
|
|
|
@@ -123,6 +108,11 @@ Limit Speed
|
|
|
123
108
|
```bash
|
|
124
109
|
ytfetch -r "500K" "VIDEO_URL"
|
|
125
110
|
```
|
|
111
|
+
Selected range Search
|
|
112
|
+
```bash
|
|
113
|
+
ytfetch "PLAYLIST_URL" -I ":50" -list "search_word"
|
|
114
|
+
```
|
|
115
|
+
*and so on...*
|
|
126
116
|
|
|
127
117
|
## 🔖 License
|
|
128
118
|
|
|
@@ -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,14 +78,17 @@ 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
|
|
87
|
+
|
|
88
|
+
output template aliases:
|
|
89
|
+
-o autonumber/num -> playlist-numbered naming
|
|
90
|
+
-o music/song -> title + artist naming
|
|
91
|
+
-o default -> default naming
|
|
91
92
|
```
|
|
92
93
|
|
|
93
94
|
### Examples
|
|
@@ -112,17 +113,6 @@ Extract Audio Only in Mp3
|
|
|
112
113
|
```bash
|
|
113
114
|
ytfetch "VIDEO_URL" -mp3
|
|
114
115
|
```
|
|
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
116
|
|
|
127
117
|
#### Listing and Searching
|
|
128
118
|
|
|
@@ -134,10 +124,6 @@ Search through a playlist
|
|
|
134
124
|
```bash
|
|
135
125
|
ytfetch "PLAYLIST_URL" -list "remix"
|
|
136
126
|
```
|
|
137
|
-
Combined Fetch & Search
|
|
138
|
-
```bash
|
|
139
|
-
ytfetch "PLAYLIST_URL" -fetch ":50" -list "search_word"
|
|
140
|
-
```
|
|
141
127
|
|
|
142
128
|
#### Passthrough Options
|
|
143
129
|
|
|
@@ -155,6 +141,11 @@ Limit Speed
|
|
|
155
141
|
```bash
|
|
156
142
|
ytfetch -r "500K" "VIDEO_URL"
|
|
157
143
|
```
|
|
144
|
+
Selected range Search
|
|
145
|
+
```bash
|
|
146
|
+
ytfetch "PLAYLIST_URL" -I ":50" -list "search_word"
|
|
147
|
+
```
|
|
148
|
+
*and so on...*
|
|
158
149
|
|
|
159
150
|
## 🔖 License
|
|
160
151
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
|
|
3
|
+
def parse_arguments():
|
|
4
|
+
parser = argparse.ArgumentParser(
|
|
5
|
+
usage = "ytfetch URL ... [OPTIONS] [yt-dlp OPTIONS]",
|
|
6
|
+
description="ytfetch: yt-dlp wrapper with some flexible args.",
|
|
7
|
+
epilog = ("output template aliases:\n"
|
|
8
|
+
"-o autonumber/num -> playlist-numbered naming\n"
|
|
9
|
+
"-o music/song -> title + artist naming\n"
|
|
10
|
+
"-o default -> default naming\n\n"
|
|
11
|
+
"Docs & issues: https://github.com/voidsnax/ytFetch"
|
|
12
|
+
),
|
|
13
|
+
formatter_class=AlignedHelpFormatter,
|
|
14
|
+
allow_abbrev=False,
|
|
15
|
+
add_help=False
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
parser.add_argument("-h", action="help", help="Show this help message and exit")
|
|
19
|
+
parser.add_argument("--help", action="store_true", help="Show yt-dlp help message and exit")
|
|
20
|
+
parser.add_argument("-q", metavar="QUALITY", nargs="?", default="default",
|
|
21
|
+
help="Video quality (e.g., 1080, 720). Default will pick best video"
|
|
22
|
+
)
|
|
23
|
+
parser.add_argument("-avcmp3", action="store_true",
|
|
24
|
+
help="Download video in AVC (h.264) & audio in mp3 format"
|
|
25
|
+
)
|
|
26
|
+
parser.add_argument("-mp3", action="store_true", help="Extract audio only as MP3")
|
|
27
|
+
parser.add_argument("-audio", action="store_true", help="Extract audio only (bestaudio)")
|
|
28
|
+
|
|
29
|
+
parser.add_argument("-list", metavar="NAME", nargs="?", const="default",
|
|
30
|
+
help="List playlist contents. Accepts values for searching across playlist"
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
args, unknown_args = parser.parse_known_args()
|
|
34
|
+
return args, unknown_args
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class AlignedHelpFormatter(argparse.HelpFormatter):
|
|
38
|
+
def __init__(self, *args, **kwargs):
|
|
39
|
+
kwargs['max_help_position'] = 35 # default is 24
|
|
40
|
+
super().__init__(*args, **kwargs)
|
|
41
|
+
def _fill_text(self, text, width, indent):
|
|
42
|
+
# Preserve newlines in Description and Epilog
|
|
43
|
+
# splitlines(keepends=True) ensures not losing of double newlines
|
|
44
|
+
return ''.join(indent + line for line in text.splitlines(keepends=True))
|
|
45
|
+
|
|
46
|
+
def _split_lines(self, text, width):
|
|
47
|
+
# Preserve newlines in help
|
|
48
|
+
return text.splitlines()
|
|
@@ -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,100 @@
|
|
|
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
|
+
elif ydl_opts.get('outtmpl', {}).get('default') in ['music', 'song']: # type: ignore
|
|
79
|
+
ydl_opts["outtmpl"] = {'default': '%(title)s - %(artist,uploader)s.%(ext)s'}
|
|
80
|
+
elif ydl_opts.get('outtmpl', {}).get('default') == 'default': # type: ignore
|
|
81
|
+
ydl_opts["outtmpl"] = {}
|
|
82
|
+
|
|
83
|
+
# --- logger ---
|
|
84
|
+
if not cli_args.list: ydl_opts['progress_hooks'] = [progress_hook]
|
|
85
|
+
if not ydl_opts.get('verbose'):
|
|
86
|
+
ydl_opts['logger'] = ListLogger() if cli_args.list else YTFetchLogger() # type: ignore
|
|
87
|
+
|
|
88
|
+
# --- download ---
|
|
89
|
+
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
|
90
|
+
ydl.download(urls)
|
|
91
|
+
|
|
92
|
+
def main():
|
|
93
|
+
try:
|
|
94
|
+
args, extra_args = parse_arguments()
|
|
95
|
+
process_urls(args, extra_args)
|
|
96
|
+
except KeyboardInterrupt:
|
|
97
|
+
print(f"\n\n{Fore.YELLOW}Process aborted by user{Fore.RESET}")
|
|
98
|
+
sys.exit(1)
|
|
99
|
+
except Exception as e:
|
|
100
|
+
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')
|
ytfetch-1.2.0/src/ytfetch/cli.py
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import argparse
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
def parse_arguments():
|
|
5
|
-
parser = argparse.ArgumentParser(
|
|
6
|
-
usage = "ytfetch URL ... [OPTIONS] [yt-dlp OPTIONS] ",
|
|
7
|
-
description="ytfetch: yt-dlp wrapper with flexible args.",
|
|
8
|
-
epilog = "type yt-dlp -h for yt-dlp options \n"
|
|
9
|
-
"Doc & issues: https://github.com/voidsnax/ytFetch",
|
|
10
|
-
formatter_class=AlignedHelpFormatter,
|
|
11
|
-
allow_abbrev=False
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
parser.add_argument("url", nargs="+", metavar="URL")
|
|
15
|
-
parser.add_argument("-avcmp3", action="store_true",
|
|
16
|
-
help="Download video in AVC (h.264) & audio in mp3 format")
|
|
17
|
-
parser.add_argument("-q", metavar="QUALITY", nargs="?", default="default",
|
|
18
|
-
help="Video quality (e.g., 1080, 720). Default will pick best video")
|
|
19
|
-
parser.add_argument("-mp3", action="store_true",
|
|
20
|
-
help="Extract audio only as MP3")
|
|
21
|
-
parser.add_argument("-audio", action="store_true",
|
|
22
|
-
help="Extract audio only (bestaudio)")
|
|
23
|
-
parser.add_argument("-fetch", metavar="RANGE", nargs="+",
|
|
24
|
-
help="Alternate for --playlist-items. Single value applies globally\n"
|
|
25
|
-
"Multiple values must match number of playlist URLs provided")
|
|
26
|
-
parser.add_argument("-list", metavar="NAME", nargs="?", const="default",
|
|
27
|
-
help="List playlist contents. Accepts values for searching across playlist")
|
|
28
|
-
|
|
29
|
-
args, unknown_args = parser.parse_known_args()
|
|
30
|
-
return args, unknown_args
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
class AlignedHelpFormatter(argparse.HelpFormatter):
|
|
34
|
-
def __init__(self, *args, **kwargs):
|
|
35
|
-
kwargs['max_help_position'] = 35 # default is 24
|
|
36
|
-
super().__init__(*args, **kwargs)
|
|
37
|
-
def _fill_text(self, text, width, indent):
|
|
38
|
-
# Preserve newlines in Description and Epilog
|
|
39
|
-
# splitlines(keepends=True) ensures not losing of double newlines
|
|
40
|
-
return ''.join(indent + line for line in text.splitlines(keepends=True))
|
|
41
|
-
|
|
42
|
-
def _split_lines(self, text, width):
|
|
43
|
-
# Preserve newlines in help
|
|
44
|
-
return text.splitlines()
|
|
@@ -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
|