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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ytfetch
3
- Version: 1.2.0
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
- # ytfetch
15
+ # ytFetch
16
16
 
17
- **ytfetch** simplifies media downloading and playlist management by adding smart Fetching, playlist searching, and streamlined format handling on top of the powerful [yt-dlp](https://github.com/yt-dlp/yt-dlp) engine.
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
- -help Show yt-dlp help message
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
- # ytfetch
1
+ # ytFetch
2
2
 
3
- **ytfetch** simplifies media downloading and playlist management by adding smart Fetching, playlist searching, and streamlined format handling on top of the powerful [yt-dlp](https://github.com/yt-dlp/yt-dlp) engine.
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 <your-repo-url>
37
- cd <your-repo>
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
- -help Show yt-dlp help messag
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 only
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
- # ytfetch
1
+ # ytFetch
2
2
 
3
- **ytfetch** simplifies media downloading and playlist management by adding smart Fetching, playlist searching, and streamlined format handling on top of the powerful [yt-dlp](https://github.com/yt-dlp/yt-dlp) engine.
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
- -help Show yt-dlp help message
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
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "ytfetch"
7
- version = "1.2.0"
7
+ version = "2.1.0"
8
8
  description = "A wrapper for yt-dlp with custom simple fetching flags"
9
9
  authors = [
10
10
  { name = "voidsnax" }
@@ -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.replace("[Merger] ", ""))
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 ErrorOnlyLogger:
41
- def debug(self, msg): pass
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')
@@ -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