DDownloader 0.3.8__tar.gz → 0.3.9__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.
- {ddownloader-0.3.8 → ddownloader-0.3.9}/DDownloader/main.py +12 -2
- ddownloader-0.3.9/DDownloader/modules/__init__.py +1 -0
- {ddownloader-0.3.8 → ddownloader-0.3.9}/DDownloader/modules/banners.py +1 -1
- {ddownloader-0.3.8 → ddownloader-0.3.9}/DDownloader/modules/downloader.py +96 -7
- {ddownloader-0.3.8 → ddownloader-0.3.9}/PKG-INFO +1 -1
- {ddownloader-0.3.8 → ddownloader-0.3.9}/pyproject.toml +2 -2
- ddownloader-0.3.8/DDownloader/modules/__init__.py +0 -1
- {ddownloader-0.3.8 → ddownloader-0.3.9}/DDownloader/__init__.py +0 -0
- {ddownloader-0.3.8 → ddownloader-0.3.9}/DDownloader/bin/N_m3u8DL-RE.exe +0 -0
- {ddownloader-0.3.8 → ddownloader-0.3.9}/DDownloader/bin/aria2c.exe +0 -0
- {ddownloader-0.3.8 → ddownloader-0.3.9}/DDownloader/bin/ffmpeg.exe +0 -0
- {ddownloader-0.3.8 → ddownloader-0.3.9}/DDownloader/bin/mkvmerge.exe +0 -0
- {ddownloader-0.3.8 → ddownloader-0.3.9}/DDownloader/bin/mp4decrypt.exe +0 -0
- {ddownloader-0.3.8 → ddownloader-0.3.9}/DDownloader/bin/shaka-packager.exe +0 -0
- {ddownloader-0.3.8 → ddownloader-0.3.9}/DDownloader/bin/yt-dlp.exe +0 -0
- {ddownloader-0.3.8 → ddownloader-0.3.9}/DDownloader/modules/args_parser.py +0 -0
- {ddownloader-0.3.8 → ddownloader-0.3.9}/DDownloader/modules/helper.py +0 -0
- {ddownloader-0.3.8 → ddownloader-0.3.9}/DDownloader/modules/streamlink.py +0 -0
- {ddownloader-0.3.8 → ddownloader-0.3.9}/LICENSE +0 -0
- {ddownloader-0.3.8 → ddownloader-0.3.9}/README.md +0 -0
@@ -72,7 +72,6 @@ def main():
|
|
72
72
|
|
73
73
|
downloader = DOWNLOADER()
|
74
74
|
|
75
|
-
# Handle downloading if URL is provided
|
76
75
|
if args.url:
|
77
76
|
if re.search(r"\.mpd\b", args.url, re.IGNORECASE):
|
78
77
|
logger.info("DASH stream detected. Initializing DASH downloader...")
|
@@ -85,8 +84,19 @@ def main():
|
|
85
84
|
print(Fore.RED + "═" * 100 + Fore.RESET)
|
86
85
|
downloader.normal_downloader(args.url, os.path.join(downloads_dir, args.output))
|
87
86
|
exit(1)
|
87
|
+
elif re.search(r"(youtube\.com|youtu\.be)", args.url, re.IGNORECASE):
|
88
|
+
logger.info("YouTube URL detected. Initializing YouTube downloader...")
|
89
|
+
print(Fore.RED + "═" * 100 + Fore.RESET)
|
90
|
+
is_playlist = "list=" in args.url
|
91
|
+
downloader.youtube_downloader(
|
92
|
+
url=args.url,
|
93
|
+
output_file=os.path.join(downloads_dir, args.output),
|
94
|
+
download_type="mp4",
|
95
|
+
playlist=is_playlist
|
96
|
+
)
|
97
|
+
exit(0)
|
88
98
|
else:
|
89
|
-
logger.error("Unsupported URL format. Please provide a valid DASH (.mpd), HLS (.m3u8),
|
99
|
+
logger.error("Unsupported URL format. Please provide a valid DASH (.mpd), HLS (.m3u8), ISM (.ism), or YouTube URL.")
|
90
100
|
exit(1)
|
91
101
|
|
92
102
|
downloader.manifest_url = args.url
|
@@ -0,0 +1 @@
|
|
1
|
+
__version__ = "0.3.9"
|
@@ -23,7 +23,7 @@ def banners():
|
|
23
23
|
stdout.write(""+Fore.YELLOW +"╔════════════════════════════════════════════════════════════════════════════╝\n")
|
24
24
|
stdout.write(""+Fore.YELLOW +"║ \x1b[38;2;255;20;147m• "+Fore.GREEN+"GITHUB "+Fore.RED+" |"+Fore.LIGHTWHITE_EX+" GITHUB.COM/THATNOTEASY "+Fore.YELLOW+"║\n")
|
25
25
|
stdout.write(""+Fore.YELLOW +"╚════════════════════════════════════════════════════════════════════════════╝\n")
|
26
|
-
print(f"{Fore.YELLOW}[DDownloader] - {Fore.GREEN}A DRM-Protected & Non-Protected Content Downloader - {Fore.RED}[V0.3.
|
26
|
+
print(f"{Fore.YELLOW}[DDownloader] - {Fore.GREEN}A DRM-Protected & Non-Protected Content Downloader - {Fore.RED}[V0.3.9] \n{Fore.RESET}")
|
27
27
|
|
28
28
|
# =========================================================================================================== #
|
29
29
|
|
@@ -29,6 +29,8 @@ class DOWNLOADER:
|
|
29
29
|
binary_name = 'N_m3u8DL-RE.exe' if platform.system() == 'Windows' else 'N_m3u8DL-RE'
|
30
30
|
elif binary_type == 'ffmpeg':
|
31
31
|
binary_name = 'ffmpeg.exe' if platform.system() == 'Windows' else 'ffmpeg'
|
32
|
+
elif binary_type == 'yt-dlp':
|
33
|
+
binary_name = 'yt-dlp.exe' if platform.system() == 'Windows' else 'yt-dlp'
|
32
34
|
else:
|
33
35
|
raise ValueError(f"Unknown binary type: {binary_type}")
|
34
36
|
|
@@ -105,7 +107,7 @@ class DOWNLOADER:
|
|
105
107
|
|
106
108
|
# =========================================================================================================== #
|
107
109
|
|
108
|
-
def re_encode_content(self, input_file, quality, codec="libx265", crf=
|
110
|
+
def re_encode_content(self, input_file, quality, codec="libx265", crf=23, preset="superfast", audio_bitrate="256k", fps=60):
|
109
111
|
resolutions = {
|
110
112
|
"HD": "1280:720",
|
111
113
|
"FHD": "1920:1080",
|
@@ -128,22 +130,26 @@ class DOWNLOADER:
|
|
128
130
|
|
129
131
|
self.binary_path = self._get_binary_path("ffmpeg")
|
130
132
|
|
131
|
-
logger.info(f"Re-encoding {input_file} to {quality} ({resolution}) using codec {codec}...")
|
133
|
+
logger.info(f"Re-encoding {input_file} to {quality} ({resolution}) at {fps} FPS using codec {codec}...")
|
132
134
|
logger.info(f"Output file: {output_file}")
|
133
135
|
|
134
136
|
os.makedirs(os.path.dirname(output_file), exist_ok=True)
|
135
137
|
|
136
|
-
# Build the ffmpeg command
|
138
|
+
# Build the ffmpeg command with multi-threading & FPS increase
|
137
139
|
command = [
|
138
140
|
self.binary_path,
|
139
141
|
"-i", f"\"{input_file}\"",
|
140
|
-
"-
|
142
|
+
"-r", str(fps), # Increase FPS
|
143
|
+
"-vf", f"scale={resolution}:flags=lanczos,unsharp=5:5:1.0:5:5:0.0,hqdn3d=1.5:1.5:6:6", # Scaling + Sharpening + Denoising
|
141
144
|
"-c:v", codec,
|
142
|
-
"-
|
145
|
+
"-b:v", "25M", # Video bitrate set to 25 Mbps for better quality
|
146
|
+
"-crf", str(crf), # CRF still included for quality control
|
143
147
|
"-preset", preset,
|
148
|
+
"-threads", "0", # Enables multi-threading (uses all available CPU cores)
|
144
149
|
"-c:a", "aac",
|
145
150
|
"-b:a", audio_bitrate,
|
146
151
|
"-movflags", "+faststart",
|
152
|
+
"-pix_fmt", "yuv444p", # Ensures compatibility
|
147
153
|
f"\"{output_file}\""
|
148
154
|
]
|
149
155
|
|
@@ -152,7 +158,7 @@ class DOWNLOADER:
|
|
152
158
|
|
153
159
|
# Check if output file exists to confirm success
|
154
160
|
if os.path.isfile(output_file):
|
155
|
-
logger.info(f"Re-encoding to {quality} completed successfully. Output saved to: {output_file}")
|
161
|
+
logger.info(f"Re-encoding to {quality} at {fps} FPS completed successfully. Output saved to: {output_file}")
|
156
162
|
return output_file
|
157
163
|
else:
|
158
164
|
logger.error(f"Re-encoding failed. Output file not created: {output_file}")
|
@@ -199,4 +205,87 @@ class DOWNLOADER:
|
|
199
205
|
print(f"Download complete: {output_file}")
|
200
206
|
|
201
207
|
except requests.exceptions.RequestException as e:
|
202
|
-
print(f"Error during download: {e}")
|
208
|
+
print(f"Error during download: {e}")
|
209
|
+
|
210
|
+
# =========================================================================================================== #
|
211
|
+
|
212
|
+
def youtube_downloader(self, url, output_file, download_type="mp4", playlist=False):
|
213
|
+
"""
|
214
|
+
Download a video, audio, or playlist from YouTube using yt-dlp.
|
215
|
+
|
216
|
+
Args:
|
217
|
+
url (str): The YouTube video or playlist URL.
|
218
|
+
output_file (str): The output file path to save the video or audio.
|
219
|
+
download_type (str): The type of download ("mp4" for video, "mp3" for audio).
|
220
|
+
playlist (bool): Whether the URL is a playlist.
|
221
|
+
"""
|
222
|
+
try:
|
223
|
+
# Get the yt-dlp binary path
|
224
|
+
yt_dlp_path = self._get_binary_path("yt-dlp")
|
225
|
+
|
226
|
+
# Determine the output file extension based on download type
|
227
|
+
if download_type == "mp3":
|
228
|
+
output_file = os.path.splitext(output_file)[0] + ".mp3"
|
229
|
+
elif download_type == "mp4":
|
230
|
+
output_file = os.path.splitext(output_file)[0] + ".mp4"
|
231
|
+
else:
|
232
|
+
logger.error(Fore.RED + f"Invalid download type: {download_type}. Use 'mp4' or 'mp3'." + Fore.RESET)
|
233
|
+
return
|
234
|
+
|
235
|
+
# Build the yt-dlp command
|
236
|
+
command = [
|
237
|
+
yt_dlp_path,
|
238
|
+
"-o", f"\"{output_file}\"", # Output file
|
239
|
+
"--no-check-certificate", # Bypass certificate verification
|
240
|
+
"--extractor-args", "youtube:player_client=android", # Force a specific extractor
|
241
|
+
]
|
242
|
+
|
243
|
+
# Add playlist-specific options if the URL is a playlist
|
244
|
+
if playlist:
|
245
|
+
command.extend([
|
246
|
+
"--yes-playlist", # Force downloading the playlist
|
247
|
+
"--output", f"\"{output_file}/%(playlist_index)s - %(title)s.%(ext)s\"", # Organize files in a folder
|
248
|
+
])
|
249
|
+
else:
|
250
|
+
command.extend([
|
251
|
+
"--no-playlist", # Ignore playlists if the URL is a single video
|
252
|
+
])
|
253
|
+
|
254
|
+
# Add audio extraction options if downloading MP3
|
255
|
+
if download_type == "mp3":
|
256
|
+
command.extend([
|
257
|
+
"--extract-audio", # Extract audio
|
258
|
+
"--audio-format", "mp3", # Convert to MP3
|
259
|
+
"--audio-quality", "0", # Best quality
|
260
|
+
])
|
261
|
+
else:
|
262
|
+
# For MP4, download the best video and audio formats and merge them
|
263
|
+
command.extend([
|
264
|
+
"-f", "bv*+ba/b", # Download best video + best audio, or fallback to best combined format
|
265
|
+
"--merge-output-format", "mp4", # Merge into MP4
|
266
|
+
])
|
267
|
+
|
268
|
+
# Add the YouTube URL
|
269
|
+
command.append(url)
|
270
|
+
|
271
|
+
# Execute the command
|
272
|
+
self._execute_command(command)
|
273
|
+
|
274
|
+
# Check if output file(s) exist to confirm success
|
275
|
+
if playlist:
|
276
|
+
if os.path.exists(output_file) and os.listdir(output_file):
|
277
|
+
logger.info(f"Playlist download completed successfully. Files saved to: {output_file}")
|
278
|
+
return output_file
|
279
|
+
else:
|
280
|
+
logger.error(f"Playlist download failed. No files were created in: {output_file}")
|
281
|
+
return None
|
282
|
+
else:
|
283
|
+
if os.path.isfile(output_file):
|
284
|
+
logger.info(f"Download from YouTube completed successfully. Output saved to: {output_file}")
|
285
|
+
return output_file
|
286
|
+
else:
|
287
|
+
logger.error(f"Download from YouTube failed. Output file not created: {output_file}")
|
288
|
+
return None
|
289
|
+
|
290
|
+
except Exception as e:
|
291
|
+
logger.error(Fore.RED + f"An unexpected error occurred: {e}" + Fore.RESET)
|
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "DDownloader"
|
7
|
-
version = "0.3.
|
7
|
+
version = "0.3.9"
|
8
8
|
description = "A downloader for DRM-protected & Non DRM-protected content."
|
9
9
|
readme = { file = "README.md", content-type = "text/markdown" }
|
10
10
|
authors = [
|
@@ -25,7 +25,7 @@ classifiers = [
|
|
25
25
|
|
26
26
|
[tool.poetry]
|
27
27
|
name = "DDownloader"
|
28
|
-
version = "0.3.
|
28
|
+
version = "0.3.9"
|
29
29
|
description = "A downloader for DRM-protected & Non DRM-protected content."
|
30
30
|
authors = ["ThatNotEasy <apidotmy@proton.me>"]
|
31
31
|
license = "MIT"
|
@@ -1 +0,0 @@
|
|
1
|
-
__version__ = "0.3.8"
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|