ytfetch 1.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.
@@ -0,0 +1,35 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*'
7
+
8
+ jobs:
9
+ publish:
10
+ name: Build and upload to PyPI
11
+ runs-on: ubuntu-latest
12
+ environment: pypi
13
+ permissions:
14
+ id-token: write
15
+ contents: read
16
+
17
+ steps:
18
+ - name: Checkout code
19
+ uses: actions/checkout@v4
20
+
21
+ - name: Set up Python
22
+ uses: actions/setup-python@v5
23
+ with:
24
+ python-version: '3.12'
25
+
26
+ - name: Install build tool
27
+ run: pip install build
28
+
29
+ - name: Build package
30
+ run: python -m build
31
+
32
+ - name: Publish to PyPI
33
+ uses: pypa/gh-action-pypi-publish@release/v1
34
+ with:
35
+ skip-existing: true
@@ -0,0 +1,4 @@
1
+ .vscode/
2
+ ytfetch-env/
3
+ __pycache__/
4
+ dist/
ytfetch-1.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 voidsnax
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
ytfetch-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,176 @@
1
+ Metadata-Version: 2.4
2
+ Name: ytfetch
3
+ Version: 1.0.0
4
+ Summary: A wrapper for yt-dlp with custom simple fetching flags
5
+ Project-URL: Homepage, https://github.com/voidsnax/reVidx
6
+ Author: voidsnax
7
+ License: MIT
8
+ License-File: LICENSE
9
+ Keywords: converter,h264,mp3,playlist,youtubedownloader
10
+ Requires-Python: >=3.8
11
+ Requires-Dist: colorama
12
+ Requires-Dist: yt-dlp>=2025.12.08
13
+ Description-Content-Type: text/markdown
14
+
15
+ # ytfetch
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.
18
+
19
+ ## ✨ Features
20
+
21
+ * **Smart Fetching:** Apply limits to multiple playlists or map specific ranges in a single download.
22
+ * **Search:** Use `-list` to display or search across single or multiple playlists contents.
23
+ * **Direct Download:** Single flag to download in legacy `avc/h.264` and `mp3` format and audio only extraction.
24
+ * **Video Quality:** Mention preffered video quality directly.
25
+ * **Native Compatibility:** Can also pass through any `yt-dlp` flag.
26
+ * **Minimal Output logs:** Clean and minimal logs.
27
+
28
+ ## 📋 Requirements
29
+
30
+ - [Python](https://www.python.org/): 3.8 or higher.
31
+ - [FFmpeg](https://ffmpeg.org/) and [FFprobe](https://ffmpeg.org/ffprobe.html): Should available in your system PATH.
32
+ Download builds from [here](https://ffmpeg.org/download.html)
33
+
34
+ <details>
35
+ <summary>or</summary>
36
+
37
+ - On Windows:
38
+ ```bash
39
+ winget install BtbN.FFmpeg.GPL
40
+ ```
41
+ or
42
+ ```bash
43
+ winget install Gyan.FFmpeg
44
+ ```
45
+
46
+ - On Termux (android):
47
+ ```bash
48
+ pkg install ffmpeg
49
+ ```
50
+
51
+ - On Mac (using Homebrew):
52
+ ```bash
53
+ brew install ffmpeg
54
+ ```
55
+
56
+ - On Arch Linux:
57
+ ```bash
58
+ sudo pacman -S ffmpeg
59
+ ```
60
+
61
+ - On Ubuntu/Debian:
62
+ ```bash
63
+ sudo apt install ffmpeg
64
+ ```
65
+ </details>
66
+ - [Deno](https://deno.com/): Or other JavaScript runtime/engine like [node.js](https://nodejs.org/en) or [bun](https://bun.sh/)
67
+
68
+ Check this [guide](https://github.com/yt-dlp/yt-dlp/wiki/EJS) for more info.
69
+
70
+
71
+ ## 📦 Installation
72
+
73
+ ### via pip
74
+
75
+ ```bash
76
+ pip install ytfetch
77
+ ```
78
+
79
+ ### via source
80
+ ```bash
81
+ git clone <your-repo-url>
82
+ cd <your-repo>
83
+ pip install -e .
84
+ ```
85
+
86
+ ## 🚀 Usage
87
+
88
+ ### General Syntax and Options
89
+
90
+ ```text
91
+ usage: Usage: ytfetch [yt-dlp OPTIONS] URL ... [OPTIONS]
92
+
93
+ options:
94
+ -h Show this help message and exit.
95
+ -help Show yt-dlp help message.
96
+ -avcmp3 Download video in AVC (h.264) format (mp4) + extract audio
97
+ -q QUALITY Video quality (e.g., 1080, 720). Default is 1080.
98
+ -mp3 Extract audio only as MP3
99
+ -audio Extract audio only
100
+ -fetch RANGE [RANGE ...] Alternate for --playlist-items. Single value applies globally
101
+ Multiple value must number of playlist URLs provided
102
+ match number of playlist URLs provided.
103
+ -list [NAME] List playlist contents. Accepts values for searching across playlist
104
+ ```
105
+
106
+ *Note: Arguments are position-independent except for `-list` and `-flag` where URLs can be mentioned after it*
107
+
108
+ ### Examples
109
+
110
+ #### Basic Downloading
111
+
112
+ Download a video at the default quality (1080p)
113
+ ```bash
114
+ ytfetch "VIDEO_URL"
115
+ ```
116
+ Download a video in 720p
117
+ ```bash
118
+ ytfetch "VIDEO_URL" -q 720
119
+ ```
120
+ #### Format Conversion
121
+
122
+ Download as AVC(H.264) and Mp3 in MP4 Container
123
+ ```bash
124
+ ytfetch "VIDEO_URL" -avcmp3
125
+ ```
126
+ Extract Audio Only in Mp3
127
+ ```bash
128
+ ytfetch "VIDEO_URL" -mp3
129
+ ```
130
+ #### Smart Fetching
131
+
132
+ Global Limit
133
+ ```bash
134
+ ytfetch "PLAYLIST_URL_1" "PLAYLIST_URL_2" -fetch "1-10"
135
+ ```
136
+ Mapped Ranges
137
+ ```bash
138
+ ytfetch "PLAYLIST_A" "PLAYLIST_B" -fetch "1-5" "10-15"
139
+ ```
140
+ *Note: The number of fetch arguments must match the number of URLs*
141
+
142
+ #### Listing and Searching
143
+
144
+ List a Playlist
145
+ ```bash
146
+ ytfetch "PLAYLIST_URL" -list
147
+ ```
148
+ Search through a playlist
149
+ ```bash
150
+ ytfetch "PLAYLIST_URL" -list "remix"
151
+ ```
152
+ Combined Fetch & Search
153
+ ```bash
154
+ ytfetch "PLAYLIST_URL" -fetch ":50" -list "search_word"
155
+ ```
156
+
157
+ #### Passthrough Options
158
+
159
+ You can append any standard yt-dlp option to your command. Both long and short flags are supported.
160
+
161
+ Download via Proxy
162
+ ```bash
163
+ ytfetch --proxy "socks5://127.0.0.1:1080" "VIDEO_URL"
164
+ ```
165
+ Use Cookies
166
+ ```bash
167
+ ytfetch --cookies "cookies.txt" "VIDEO_URL"
168
+ ```
169
+ Limit Speed
170
+ ```bash
171
+ ytfetch -r "500K" "VIDEO_URL"
172
+ ```
173
+
174
+ ## 🔖 License
175
+
176
+ MIT License
@@ -0,0 +1,129 @@
1
+ # ytfetch
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.
4
+
5
+ ## ✨ Features
6
+
7
+ * **Smart Fetching:** Apply limits to multiple playlists or map specific ranges in a single download.
8
+ * **Search:** Use `-list` to display or search across single or multiple playlists contents.
9
+ * **Direct Download:** Single flag to download in legacy `avc/h.264` and `mp3` format and audio only extraction.
10
+ * **Video Quality:** Mention preffered video quality directly.
11
+ * **Native Compatibility:** Can also pass through any `yt-dlp` flag.
12
+ * **Minimal Output logs:** Clean and minimal logs.
13
+
14
+ ## 📋 Requirements
15
+
16
+ - [Python](https://www.python.org/): 3.8 or higher.
17
+ - [FFmpeg](https://ffmpeg.org/) and [FFprobe](https://ffmpeg.org/ffprobe.html): Should available in your system PATH.
18
+ Download builds from [here](https://ffmpeg.org/download.html)
19
+ - [Deno](https://deno.com/): Or other JavaScript runtime/engine like [node.js](https://nodejs.org/en) or [bun](https://bun.sh/)
20
+
21
+ Check this [guide](https://github.com/yt-dlp/yt-dlp/wiki/EJS) for more info.
22
+
23
+
24
+ ## 📦 Installation
25
+
26
+ ### via pip
27
+
28
+ ```bash
29
+ pip install ytfetch
30
+ ```
31
+
32
+ ### via source
33
+ ```bash
34
+ git clone <your-repo-url>
35
+ cd <your-repo>
36
+ pip install -e .
37
+ ```
38
+
39
+ ## 🚀 Usage
40
+
41
+ ### General Syntax and Options
42
+
43
+ ```text
44
+ usage: Usage: ytfetch [yt-dlp OPTIONS] URL ... [OPTIONS]
45
+
46
+ options:
47
+ -h Show this help message and exit.
48
+ -help Show yt-dlp help message.
49
+ -avcmp3 Download video in AVC (h.264) format (mp4) + extract audio
50
+ -q QUALITY Video quality (e.g., 1080, 720). Default is 1080.
51
+ -mp3 Extract audio only as MP3
52
+ -audio Extract audio only
53
+ -fetch RANGE [RANGE ...] Alternate for --playlist-items. Single value applies globally
54
+ Multiple value must number of playlist URLs provided
55
+ match number of playlist URLs provided.
56
+ -list [NAME] List playlist contents. Accepts values for searching across playlist
57
+ ```
58
+
59
+ *Note: Arguments are position-independent except for `-list` and `-flag` where URLs can be mentioned after it*
60
+
61
+ ### Examples
62
+
63
+ #### Basic Downloading
64
+
65
+ Download a video at the default quality (1080p)
66
+ ```bash
67
+ ytfetch "VIDEO_URL"
68
+ ```
69
+ Download a video in 720p
70
+ ```bash
71
+ ytfetch "VIDEO_URL" -q 720
72
+ ```
73
+ #### Format Conversion
74
+
75
+ Download as AVC(H.264) and Mp3 in MP4 Container
76
+ ```bash
77
+ ytfetch "VIDEO_URL" -avcmp3
78
+ ```
79
+ Extract Audio Only in Mp3
80
+ ```bash
81
+ ytfetch "VIDEO_URL" -mp3
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
+
95
+ #### Listing and Searching
96
+
97
+ List a Playlist
98
+ ```bash
99
+ ytfetch "PLAYLIST_URL" -list
100
+ ```
101
+ Search through a playlist
102
+ ```bash
103
+ ytfetch "PLAYLIST_URL" -list "remix"
104
+ ```
105
+ Combined Fetch & Search
106
+ ```bash
107
+ ytfetch "PLAYLIST_URL" -fetch ":50" -list "search_word"
108
+ ```
109
+
110
+ #### Passthrough Options
111
+
112
+ You can append any standard yt-dlp option to your command. Both long and short flags are supported.
113
+
114
+ Download via Proxy
115
+ ```bash
116
+ ytfetch --proxy "socks5://127.0.0.1:1080" "VIDEO_URL"
117
+ ```
118
+ Use Cookies
119
+ ```bash
120
+ ytfetch --cookies "cookies.txt" "VIDEO_URL"
121
+ ```
122
+ Limit Speed
123
+ ```bash
124
+ ytfetch -r "500K" "VIDEO_URL"
125
+ ```
126
+
127
+ ## 🔖 License
128
+
129
+ MIT License
@@ -0,0 +1,162 @@
1
+ # ytfetch
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.
4
+
5
+ ## ✨ Features
6
+
7
+ * **Smart Fetching:** Apply limits to multiple playlists or map specific ranges in a single download.
8
+ * **Search:** Use `-list` to display or search across single or multiple playlists contents.
9
+ * **Direct Download:** Single flag to download in legacy `avc/h.264` and `mp3` format and audio only extraction.
10
+ * **Video Quality:** Mention preffered video quality directly.
11
+ * **Native Compatibility:** Can also pass through any `yt-dlp` flag.
12
+ * **Minimal Output logs:** Clean and minimal logs.
13
+
14
+ ## 📋 Requirements
15
+
16
+ - [Python](https://www.python.org/): 3.8 or higher.
17
+ - [FFmpeg](https://ffmpeg.org/) and [FFprobe](https://ffmpeg.org/ffprobe.html): Should available in your system PATH.
18
+ Download builds from [here](https://ffmpeg.org/download.html)
19
+
20
+ <details>
21
+ <summary>or</summary>
22
+
23
+ - On Windows:
24
+ ```bash
25
+ winget install BtbN.FFmpeg.GPL
26
+ ```
27
+ or
28
+ ```bash
29
+ winget install Gyan.FFmpeg
30
+ ```
31
+
32
+ - On Termux (android):
33
+ ```bash
34
+ pkg install ffmpeg
35
+ ```
36
+
37
+ - On Mac (using Homebrew):
38
+ ```bash
39
+ brew install ffmpeg
40
+ ```
41
+
42
+ - On Arch Linux:
43
+ ```bash
44
+ sudo pacman -S ffmpeg
45
+ ```
46
+
47
+ - On Ubuntu/Debian:
48
+ ```bash
49
+ sudo apt install ffmpeg
50
+ ```
51
+ </details>
52
+ - [Deno](https://deno.com/): Or other JavaScript runtime/engine like [node.js](https://nodejs.org/en) or [bun](https://bun.sh/)
53
+
54
+ Check this [guide](https://github.com/yt-dlp/yt-dlp/wiki/EJS) for more info.
55
+
56
+
57
+ ## 📦 Installation
58
+
59
+ ### via pip
60
+
61
+ ```bash
62
+ pip install ytfetch
63
+ ```
64
+
65
+ ### via source
66
+ ```bash
67
+ git clone <your-repo-url>
68
+ cd <your-repo>
69
+ pip install -e .
70
+ ```
71
+
72
+ ## 🚀 Usage
73
+
74
+ ### General Syntax and Options
75
+
76
+ ```text
77
+ usage: Usage: ytfetch [yt-dlp OPTIONS] URL ... [OPTIONS]
78
+
79
+ options:
80
+ -h Show this help message and exit.
81
+ -help Show yt-dlp help message.
82
+ -avcmp3 Download video in AVC (h.264) format (mp4) + extract audio
83
+ -q QUALITY Video quality (e.g., 1080, 720). Default is 1080.
84
+ -mp3 Extract audio only as MP3
85
+ -audio Extract audio only
86
+ -fetch RANGE [RANGE ...] Alternate for --playlist-items. Single value applies globally
87
+ Multiple value must number of playlist URLs provided
88
+ match number of playlist URLs provided.
89
+ -list [NAME] List playlist contents. Accepts values for searching across playlist
90
+ ```
91
+
92
+ *Note: Arguments are position-independent except for `-list` and `-flag` where URLs can be mentioned after it*
93
+
94
+ ### Examples
95
+
96
+ #### Basic Downloading
97
+
98
+ Download a video at the default quality (1080p)
99
+ ```bash
100
+ ytfetch "VIDEO_URL"
101
+ ```
102
+ Download a video in 720p
103
+ ```bash
104
+ ytfetch "VIDEO_URL" -q 720
105
+ ```
106
+ #### Format Conversion
107
+
108
+ Download as AVC(H.264) and Mp3 in MP4 Container
109
+ ```bash
110
+ ytfetch "VIDEO_URL" -avcmp3
111
+ ```
112
+ Extract Audio Only in Mp3
113
+ ```bash
114
+ ytfetch "VIDEO_URL" -mp3
115
+ ```
116
+ #### Smart Fetching
117
+
118
+ Global Limit
119
+ ```bash
120
+ ytfetch "PLAYLIST_URL_1" "PLAYLIST_URL_2" -fetch "1-10"
121
+ ```
122
+ Mapped Ranges
123
+ ```bash
124
+ ytfetch "PLAYLIST_A" "PLAYLIST_B" -fetch "1-5" "10-15"
125
+ ```
126
+ *Note: The number of fetch arguments must match the number of URLs*
127
+
128
+ #### Listing and Searching
129
+
130
+ List a Playlist
131
+ ```bash
132
+ ytfetch "PLAYLIST_URL" -list
133
+ ```
134
+ Search through a playlist
135
+ ```bash
136
+ ytfetch "PLAYLIST_URL" -list "remix"
137
+ ```
138
+ Combined Fetch & Search
139
+ ```bash
140
+ ytfetch "PLAYLIST_URL" -fetch ":50" -list "search_word"
141
+ ```
142
+
143
+ #### Passthrough Options
144
+
145
+ You can append any standard yt-dlp option to your command. Both long and short flags are supported.
146
+
147
+ Download via Proxy
148
+ ```bash
149
+ ytfetch --proxy "socks5://127.0.0.1:1080" "VIDEO_URL"
150
+ ```
151
+ Use Cookies
152
+ ```bash
153
+ ytfetch --cookies "cookies.txt" "VIDEO_URL"
154
+ ```
155
+ Limit Speed
156
+ ```bash
157
+ ytfetch -r "500K" "VIDEO_URL"
158
+ ```
159
+
160
+ ## 🔖 License
161
+
162
+ MIT License
@@ -0,0 +1,29 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "ytfetch"
7
+ version = "1.0.0"
8
+ description = "A wrapper for yt-dlp with custom simple fetching flags"
9
+ authors = [
10
+ { name = "voidsnax" }
11
+ ]
12
+ readme = "README.md"
13
+ license = { text = "MIT" }
14
+ requires-python = ">=3.8"
15
+ dependencies = [
16
+ "yt-dlp>=2025.12.08",
17
+ "colorama"
18
+ ]
19
+
20
+ keywords = ["youtubedownloader", "converter", "h264", "mp3", "playlist"]
21
+
22
+ [project.urls]
23
+ Homepage = "https://github.com/voidsnax/reVidx"
24
+
25
+ [project.scripts]
26
+ ytfetch = "ytfetch.cli:main"
27
+
28
+ [tool.hatch.build.targets.wheel]
29
+ packages = ["src/ytfetch"]
File without changes
@@ -0,0 +1,247 @@
1
+ import argparse
2
+ import sys
3
+ import yt_dlp # type: ignore
4
+ from colorama import Fore, Style # type: ignore
5
+ import subprocess
6
+ from .utils import (
7
+ validate_fetch_ranges,
8
+ ErrorOnlyLogger,
9
+ AlignedHelpFormatter,
10
+ print_error,
11
+ parse_passthrough_args
12
+ )
13
+
14
+ # --- Custom Logger ---
15
+ class YTFetchLogger:
16
+ def debug(self, msg):
17
+ message = [
18
+ "Downloading playlist:", "Downloading item", "Resuming",
19
+ "Destination:", "already been downloaded", "Finished downloading"
20
+ ]
21
+ clean_msg = msg.replace("[download] ", "")
22
+
23
+ if any(phrase in msg for phrase in message):
24
+ print(clean_msg)
25
+
26
+ if "Merging formats" in msg:
27
+ print(msg.replace("[Merger] ", ""))
28
+
29
+ def info(self, msg):
30
+ pass
31
+
32
+ def warning(self, msg):
33
+ if "Some web client https formats have been skipped" in msg:
34
+ return
35
+ print(f"{msg}")
36
+
37
+ def error(self, msg):
38
+ print(f"{msg}")
39
+
40
+
41
+ # --- Custom Progress Hook ---
42
+ def progress_hook(d):
43
+ if d['status'] == 'downloading':
44
+ percent = d.get('_percent_str', '0.0%')
45
+ total = d.get('_total_bytes_str', d.get('_total_bytes_estimate_str', 'N/A'))
46
+ speed = d.get('_speed_str', 'N/A')
47
+ status = f"{percent} of {total} at {speed}"
48
+ print(f"\r{status}", end="")
49
+ sys.stdout.flush()
50
+ elif d['status'] == 'finished':
51
+ print()
52
+
53
+ # --- Argument Parsing ---
54
+ def parse_arguments():
55
+ parser = argparse.ArgumentParser(
56
+ usage = "ytfetch [yt-dlp OPTIONS] URL ... [OPTIONS]",
57
+ description="ytfetch: yt-dlp wrapper with flexible args.",
58
+ epilog = "type ytftech -help for yt-dlp options \n"
59
+ "Doc & issues: https://github.com/voidsnax/ytFetch",
60
+ formatter_class=AlignedHelpFormatter,
61
+ add_help=False,
62
+ allow_abbrev=False
63
+ )
64
+ parser.add_argument('-h',action='help',
65
+ help='Show this help message and exit.')
66
+ parser.add_argument('-help',action="store_const",const="default",
67
+ help='Show yt-dlp help message.')
68
+
69
+ parser.add_argument("-avcmp3", action="store_true",
70
+ help="Download video in AVC (h.264) format (mp4) + extract audio")
71
+ parser.add_argument("-q", metavar="QUALITY",default="1080",
72
+ help="Video quality (e.g., 1080, 720). Default is 1080.")
73
+ parser.add_argument("-mp3", action="store_true",
74
+ help="Extract audio only as MP3")
75
+ parser.add_argument("-audio", action="store_true",
76
+ help="Extract audio only (bestaudio)")
77
+ parser.add_argument("-fetch", metavar="RANGE",nargs="+",
78
+ help="Alternate for --playlist-items. Single value applies globally\n"
79
+ "Multiple values must match number of playlist URLs provided")
80
+ parser.add_argument("-list", metavar="NAME",nargs="?",const="default",
81
+ help="List playlist contents. Accepts values for searching across playlist")
82
+
83
+ args, unknown_args = parser.parse_known_args()
84
+ return args, unknown_args
85
+
86
+ def get_urls_from_args(args_list):
87
+ return [a for a in args_list if a.startswith("http")]
88
+
89
+ def get_format_selector(args):
90
+ height = args.q.rstrip("p")
91
+
92
+ if args.mp3 or args.audio:
93
+ return "bestaudio"
94
+
95
+ if args.avcmp3:
96
+ return f"bestvideo[vcodec^=avc1][height<={height}]+bestaudio/best[vcodec^=avc1][height<={height}]"
97
+ return f"bestvideo[height<={height}]+bestaudio/best[height<={height}]"
98
+
99
+
100
+ def process_urls(custom_args, raw_ytdlp_args):
101
+ urls = get_urls_from_args(raw_ytdlp_args)
102
+ if custom_args.help:
103
+ yt_dlp.options.create_parser().print_help() # type: ignore
104
+ sys.exit()
105
+ if custom_args.list and custom_args.list.startswith('http'):
106
+ print_error(f"Provided URL link as argument for -list\nUse -list after URL")
107
+ sys.exit(1)
108
+ if custom_args.fetch and any(item.startswith("http") for item in custom_args.fetch):
109
+ print_error(f"Provided a URL link after -fetch\nUse -fetch after URL")
110
+ sys.exit(1)
111
+ if not urls:
112
+ print_error(f"Error: No URLs provided.")
113
+ sys.exit(1)
114
+
115
+ passthrough_opts = parse_passthrough_args(raw_ytdlp_args)
116
+
117
+ ydl_opts = {
118
+ 'outtmpl': '%(title)s.%(ext)s',
119
+ 'logger': YTFetchLogger(),
120
+ 'progress_hooks': [progress_hook],
121
+ }
122
+
123
+ ydl_opts.update(passthrough_opts)
124
+
125
+ # --- Mode: List ---
126
+ if custom_args.list:
127
+ base_cmd = [
128
+ "yt-dlp",
129
+ "--flat-playlist",
130
+ "--print", "%(playlist_index)s - %(title)s"
131
+ ]
132
+
133
+ passthrough_args = [arg for arg in raw_ytdlp_args if arg not in urls]
134
+ base_cmd.extend(passthrough_args)
135
+ command = []
136
+
137
+ ydl_opts['extract_flat'] = 'in_playlist'
138
+ ydl_opts['logger'] = ErrorOnlyLogger()
139
+
140
+ search_mode = False
141
+
142
+ fetch_ranges = custom_args.fetch
143
+ if fetch_ranges:
144
+ if len(fetch_ranges) >1:
145
+ validate_fetch_ranges(fetch_ranges,urls)
146
+ for fetch_range in fetch_ranges:
147
+ command.append(base_cmd + [ "--playlist-items", fetch_range])
148
+ else:
149
+ command.append(base_cmd + ["--playlist-items", fetch_ranges[0]])
150
+ else:
151
+ command.append(base_cmd)
152
+
153
+ # make sure command is a list of list
154
+ if len(command) == 1 and len(urls) > 1:
155
+ command = [cmd.copy() for cmd in [command[0]] * len(urls)]
156
+
157
+ # print(command)
158
+
159
+ if custom_args.list != 'default':
160
+ search_mode = True
161
+ list_val = custom_args.list
162
+ search_pattern = list_val.lower()
163
+ for url, cmd in zip(urls, command):
164
+ # currently for title fetching this is more reliable
165
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl: #type: ignore
166
+ try:
167
+ info = ydl.extract_info(url, download=False) # type: ignore
168
+ if 'entries' not in info:
169
+ print(f"Single Video: {info.get('title')}")
170
+ else:
171
+ title = f"▶ Playlist: {info.get('title')}"
172
+ print(f"▶ Playlist: {Fore.CYAN}{info.get('title')}{Style.RESET_ALL}")
173
+ print(f"{"-" * len(title)}")
174
+ except Exception as e:
175
+ print(f"Error getting {url} title: {e}")
176
+ try:
177
+ cmd.append(url)
178
+ # print(cmd)
179
+ result = subprocess.run(cmd, capture_output=True, text=True)
180
+ found_match = False
181
+ for line in result.stdout.splitlines():
182
+ idx, title = line.split(" - ", 1)
183
+ video = f"{Fore.YELLOW}{idx}{Style.RESET_ALL} - {title}"
184
+ if search_mode:
185
+ if search_pattern in line.lower():
186
+ found_match = True
187
+ print(video)
188
+ else:
189
+ print(video)
190
+ if not found_match and search_mode:
191
+ print(f"No match found for {Fore.YELLOW}{search_pattern}{Style.RESET_ALL}")
192
+ except Exception as e:
193
+ print_error(f"Error listing {url}: {e}")
194
+
195
+ # stops further execution
196
+ return
197
+
198
+ ydl_opts['format'] = get_format_selector(custom_args)
199
+
200
+ if custom_args.mp3:
201
+ ydl_opts['postprocessors'] = [{
202
+ 'key': 'FFmpegExtractAudio',
203
+ 'preferredcodec': 'mp3',
204
+ 'preferredquality': '192',
205
+ }]
206
+
207
+ if custom_args.avcmp3 and not custom_args.mp3:
208
+ ydl_opts['merge_output_format'] = 'mp4'
209
+ ydl_opts['postprocessor_args'] = {
210
+ 'ffmpeg': ['-c:v', 'copy', '-c:a', 'libmp3lame', '-b:a', '192k']
211
+ }
212
+
213
+ # --- Mode: Download ---
214
+ def run_download(url, playlist_items=None):
215
+ opts = ydl_opts.copy()
216
+ if playlist_items:
217
+ opts['playlist_items'] = playlist_items
218
+ with yt_dlp.YoutubeDL(opts) as ydl: #type:ignore
219
+ try:
220
+ ydl.download([url])
221
+ except Exception as e:
222
+ # print(f"Download error for {url}: {e}")
223
+ pass
224
+
225
+ fetch_ranges = custom_args.fetch
226
+ if fetch_ranges:
227
+ if len(fetch_ranges) == 1:
228
+ for url in urls:
229
+ run_download(url, fetch_ranges[0])
230
+ else:
231
+ validate_fetch_ranges(fetch_ranges,urls)
232
+ for url, rng in zip(urls, fetch_ranges):
233
+ run_download(url, rng)
234
+ else:
235
+ for url in urls:
236
+ run_download(url)
237
+
238
+ def main():
239
+ try:
240
+ custom_args, ytdlp_args = parse_arguments()
241
+ process_urls(custom_args, ytdlp_args)
242
+ except KeyboardInterrupt:
243
+ print(f"\n\n{Fore.YELLOW}Process aborted by user.{Style.RESET_ALL}")
244
+ sys.exit(1)
245
+
246
+ if __name__ == "__main__":
247
+ main()
@@ -0,0 +1,64 @@
1
+ import sys
2
+ import argparse
3
+ import yt_dlp
4
+ from colorama import Fore, init # type: ignore
5
+
6
+ init(autoreset=True)
7
+
8
+ def validate_fetch_ranges(fetch_ranges, urls):
9
+ if len(fetch_ranges) != len(urls):
10
+ print_error(f"Error: Provided {len(fetch_ranges)} fetch ranges for {len(urls)} URLs.")
11
+ sys.exit(1)
12
+
13
+ class ErrorOnlyLogger:
14
+ def debug(self, msg): pass
15
+ def info(self, msg): pass
16
+ def warning(self, msg): pass
17
+ def error(self, msg): print(msg)
18
+
19
+ class AlignedHelpFormatter(argparse.HelpFormatter):
20
+ def __init__(self, *args, **kwargs):
21
+ kwargs['max_help_position'] = 35 # default is 24
22
+ super().__init__(*args, **kwargs)
23
+ def _fill_text(self, text, width, indent):
24
+ # Preserve newlines in Description and Epilog
25
+ # splitlines(keepends=True) ensures not losing of double newlines
26
+ return ''.join(indent + line for line in text.splitlines(keepends=True))
27
+
28
+ def _split_lines(self, text, width):
29
+ # Preserve newlines in help
30
+ return text.splitlines()
31
+
32
+
33
+ def print_error(msg):
34
+ print(f"{Fore.RED}{msg}")
35
+
36
+ def parse_passthrough_args(args_list):
37
+ """
38
+ Parses unknown arguments using yt-dlp's internal parser.
39
+ Returns a dictionary containing ONLY the options explicitly passed by the user.
40
+ """
41
+ filtered_args = [a for a in args_list if not a.startswith("http")]
42
+ if not filtered_args:
43
+ return {}
44
+
45
+ parser = yt_dlp.options.create_parser() # type: ignore
46
+
47
+ parsed, _ = parser.parse_known_args(filtered_args)
48
+
49
+ opts = vars(parsed) # convert to dict
50
+
51
+ # Filter out defaults to return ONLY user-provided args
52
+ # create a baseline parser to see what the defaults are
53
+ base_parser = yt_dlp.options.create_parser() # type: ignore
54
+ base_parsed, _ = base_parser.parse_args([])
55
+ base_opts = vars(base_parsed)
56
+
57
+ # Build dictionary of only changed items
58
+ final_opts = {}
59
+ for key, value in opts.items():
60
+ # Only keep the option if the user changed it from the default
61
+ if base_opts[key] != value:
62
+ final_opts[key] = value
63
+
64
+ return final_opts