DDownloader 0.3.8__tar.gz → 0.4.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.
- {ddownloader-0.3.8 → ddownloader-0.4.0}/DDownloader/main.py +12 -2
- ddownloader-0.4.0/DDownloader/modules/__init__.py +1 -0
- {ddownloader-0.3.8 → ddownloader-0.4.0}/DDownloader/modules/banners.py +1 -1
- {ddownloader-0.3.8 → ddownloader-0.4.0}/DDownloader/modules/downloader.py +109 -8
- {ddownloader-0.3.8 → ddownloader-0.4.0}/PKG-INFO +1 -1
- {ddownloader-0.3.8 → ddownloader-0.4.0}/pyproject.toml +2 -2
- ddownloader-0.3.8/DDownloader/modules/__init__.py +0 -1
- {ddownloader-0.3.8 → ddownloader-0.4.0}/DDownloader/__init__.py +0 -0
- {ddownloader-0.3.8 → ddownloader-0.4.0}/DDownloader/bin/N_m3u8DL-RE.exe +0 -0
- {ddownloader-0.3.8 → ddownloader-0.4.0}/DDownloader/bin/aria2c.exe +0 -0
- {ddownloader-0.3.8 → ddownloader-0.4.0}/DDownloader/bin/ffmpeg.exe +0 -0
- {ddownloader-0.3.8 → ddownloader-0.4.0}/DDownloader/bin/mkvmerge.exe +0 -0
- {ddownloader-0.3.8 → ddownloader-0.4.0}/DDownloader/bin/mp4decrypt.exe +0 -0
- {ddownloader-0.3.8 → ddownloader-0.4.0}/DDownloader/bin/shaka-packager.exe +0 -0
- {ddownloader-0.3.8 → ddownloader-0.4.0}/DDownloader/bin/yt-dlp.exe +0 -0
- {ddownloader-0.3.8 → ddownloader-0.4.0}/DDownloader/modules/args_parser.py +0 -0
- {ddownloader-0.3.8 → ddownloader-0.4.0}/DDownloader/modules/helper.py +0 -0
- {ddownloader-0.3.8 → ddownloader-0.4.0}/DDownloader/modules/streamlink.py +0 -0
- {ddownloader-0.3.8 → ddownloader-0.4.0}/LICENSE +0 -0
- {ddownloader-0.3.8 → ddownloader-0.4.0}/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.4.0"
|
@@ -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.
|
26
|
+
print(f"{Fore.YELLOW}[DDownloader] - {Fore.GREEN}A DRM-Protected & Non-Protected Content Downloader - {Fore.RED}[V0.4.0] \n{Fore.RESET}")
|
27
27
|
|
28
28
|
# =========================================================================================================== #
|
29
29
|
|
@@ -29,16 +29,29 @@ 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
|
|
37
|
+
# First check project's bin directory
|
35
38
|
binary_path = os.path.join(bin_dir, binary_name)
|
39
|
+
|
40
|
+
# For ffmpeg on Linux, fall back to system path if not found in project
|
41
|
+
if binary_type == 'ffmpeg' and platform.system() == 'Linux' and not os.path.isfile(binary_path):
|
42
|
+
system_ffmpeg = '/usr/bin/ffmpeg'
|
43
|
+
if os.path.isfile(system_ffmpeg):
|
44
|
+
binary_path = system_ffmpeg
|
45
|
+
logger.info(Fore.YELLOW + f"Using system ffmpeg at: {binary_path}" + Fore.RESET)
|
46
|
+
else:
|
47
|
+
logger.error(f"ffmpeg not found in project bin or system path")
|
48
|
+
raise FileNotFoundError(f"ffmpeg not found in project bin or system path")
|
36
49
|
|
37
50
|
if not os.path.isfile(binary_path):
|
38
51
|
logger.error(f"Binary not found: {binary_path}")
|
39
52
|
raise FileNotFoundError(f"Binary not found: {binary_path}")
|
40
53
|
|
41
|
-
if platform.system() == 'Linux':
|
54
|
+
if platform.system() == 'Linux' and not binary_path.startswith('/usr/bin/'):
|
42
55
|
chmod_command = ['chmod', '+x', binary_path]
|
43
56
|
try:
|
44
57
|
subprocess.run(chmod_command, check=True)
|
@@ -105,7 +118,7 @@ class DOWNLOADER:
|
|
105
118
|
|
106
119
|
# =========================================================================================================== #
|
107
120
|
|
108
|
-
def re_encode_content(self, input_file, quality, codec="libx265", crf=
|
121
|
+
def re_encode_content(self, input_file, quality, codec="libx265", crf=23, preset="superfast", audio_bitrate="256k", fps=60):
|
109
122
|
resolutions = {
|
110
123
|
"HD": "1280:720",
|
111
124
|
"FHD": "1920:1080",
|
@@ -128,22 +141,26 @@ class DOWNLOADER:
|
|
128
141
|
|
129
142
|
self.binary_path = self._get_binary_path("ffmpeg")
|
130
143
|
|
131
|
-
logger.info(f"Re-encoding {input_file} to {quality} ({resolution}) using codec {codec}...")
|
144
|
+
logger.info(f"Re-encoding {input_file} to {quality} ({resolution}) at {fps} FPS using codec {codec}...")
|
132
145
|
logger.info(f"Output file: {output_file}")
|
133
146
|
|
134
147
|
os.makedirs(os.path.dirname(output_file), exist_ok=True)
|
135
148
|
|
136
|
-
# Build the ffmpeg command
|
149
|
+
# Build the ffmpeg command with multi-threading & FPS increase
|
137
150
|
command = [
|
138
151
|
self.binary_path,
|
139
152
|
"-i", f"\"{input_file}\"",
|
140
|
-
"-
|
153
|
+
"-r", str(fps), # Increase FPS
|
154
|
+
"-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
155
|
"-c:v", codec,
|
142
|
-
"-
|
156
|
+
"-b:v", "25M", # Video bitrate set to 25 Mbps for better quality
|
157
|
+
"-crf", str(crf), # CRF still included for quality control
|
143
158
|
"-preset", preset,
|
159
|
+
"-threads", "0", # Enables multi-threading (uses all available CPU cores)
|
144
160
|
"-c:a", "aac",
|
145
161
|
"-b:a", audio_bitrate,
|
146
162
|
"-movflags", "+faststart",
|
163
|
+
"-pix_fmt", "yuv444p", # Ensures compatibility
|
147
164
|
f"\"{output_file}\""
|
148
165
|
]
|
149
166
|
|
@@ -152,7 +169,7 @@ class DOWNLOADER:
|
|
152
169
|
|
153
170
|
# Check if output file exists to confirm success
|
154
171
|
if os.path.isfile(output_file):
|
155
|
-
logger.info(f"Re-encoding to {quality} completed successfully. Output saved to: {output_file}")
|
172
|
+
logger.info(f"Re-encoding to {quality} at {fps} FPS completed successfully. Output saved to: {output_file}")
|
156
173
|
return output_file
|
157
174
|
else:
|
158
175
|
logger.error(f"Re-encoding failed. Output file not created: {output_file}")
|
@@ -199,4 +216,88 @@ class DOWNLOADER:
|
|
199
216
|
print(f"Download complete: {output_file}")
|
200
217
|
|
201
218
|
except requests.exceptions.RequestException as e:
|
202
|
-
print(f"Error during download: {e}")
|
219
|
+
print(f"Error during download: {e}")
|
220
|
+
|
221
|
+
# =========================================================================================================== #
|
222
|
+
|
223
|
+
def youtube_downloader(self, url, output_file, download_type="mp4", playlist=False):
|
224
|
+
"""
|
225
|
+
Download a video, audio, or playlist from YouTube using yt-dlp.
|
226
|
+
|
227
|
+
Args:
|
228
|
+
url (str): The YouTube video or playlist URL.
|
229
|
+
output_file (str): The output file path to save the video or audio.
|
230
|
+
download_type (str): The type of download ("mp4" for video, "mp3" for audio).
|
231
|
+
playlist (bool): Whether the URL is a playlist.
|
232
|
+
"""
|
233
|
+
try:
|
234
|
+
# Get the yt-dlp binary path
|
235
|
+
yt_dlp_path = self._get_binary_path("yt-dlp")
|
236
|
+
|
237
|
+
# Determine the output file extension based on download type
|
238
|
+
if download_type == "mp3":
|
239
|
+
output_file = os.path.splitext(output_file)[0] + ".mp3"
|
240
|
+
elif download_type == "mp4":
|
241
|
+
output_file = os.path.splitext(output_file)[0] + ".mp4"
|
242
|
+
else:
|
243
|
+
logger.error(Fore.RED + f"Invalid download type: {download_type}. Use 'mp4' or 'mp3'." + Fore.RESET)
|
244
|
+
return
|
245
|
+
|
246
|
+
# Build the yt-dlp command
|
247
|
+
command = [
|
248
|
+
yt_dlp_path,
|
249
|
+
"-o", f"\"{output_file}\"", # Output file
|
250
|
+
"--no-check-certificate", # Bypass certificate verification
|
251
|
+
"--extractor-args", "youtube:player_client=android", # Force a specific extractor
|
252
|
+
"--ignore-errors", # Ignore errors and continue downloading
|
253
|
+
]
|
254
|
+
|
255
|
+
# Add playlist-specific options if the URL is a playlist
|
256
|
+
if playlist:
|
257
|
+
command.extend([
|
258
|
+
"--yes-playlist", # Force downloading the playlist
|
259
|
+
"--output", f"\"{output_file}/%(playlist_index)s - %(title)s.%(ext)s\"", # Organize files in a folder
|
260
|
+
])
|
261
|
+
else:
|
262
|
+
command.extend([
|
263
|
+
"--no-playlist", # Ignore playlists if the URL is a single video
|
264
|
+
])
|
265
|
+
|
266
|
+
# Add audio extraction options if downloading MP3
|
267
|
+
if download_type == "mp3":
|
268
|
+
command.extend([
|
269
|
+
"--extract-audio", # Extract audio
|
270
|
+
"--audio-format", "mp3", # Convert to MP3
|
271
|
+
"--audio-quality", "0", # Best quality
|
272
|
+
])
|
273
|
+
else:
|
274
|
+
# For MP4, download the best video and audio formats and merge them
|
275
|
+
command.extend([
|
276
|
+
"-f", "bv*+ba/b", # Download best video + best audio, or fallback to best combined format
|
277
|
+
"--merge-output-format", "mp4", # Merge into MP4
|
278
|
+
])
|
279
|
+
|
280
|
+
# Add the YouTube URL
|
281
|
+
command.append(url)
|
282
|
+
|
283
|
+
# Execute the command
|
284
|
+
self._execute_command(command)
|
285
|
+
|
286
|
+
# Check if output file(s) exist to confirm success
|
287
|
+
if playlist:
|
288
|
+
if os.path.exists(output_file) and os.listdir(output_file):
|
289
|
+
logger.info(f"Playlist download completed successfully. Files saved to: {output_file}")
|
290
|
+
return output_file
|
291
|
+
else:
|
292
|
+
logger.error(f"Playlist download failed. No files were created in: {output_file}")
|
293
|
+
return None
|
294
|
+
else:
|
295
|
+
if os.path.isfile(output_file):
|
296
|
+
logger.info(f"Download from YouTube completed successfully. Output saved to: {output_file}")
|
297
|
+
return output_file
|
298
|
+
else:
|
299
|
+
logger.error(f"Download from YouTube failed. Output file not created: {output_file}")
|
300
|
+
return None
|
301
|
+
|
302
|
+
except Exception as e:
|
303
|
+
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.
|
7
|
+
version = "0.4.0"
|
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.
|
28
|
+
version = "0.4.0"
|
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
|