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.
@@ -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), or ISM (.ism) URL.")
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.8] \n{Fore.RESET}")
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=20, preset="medium", audio_bitrate="256k"):
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
- "-vf", f"scale={resolution}",
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
- "-crf", str(crf),
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)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: DDownloader
3
- Version: 0.3.8
3
+ Version: 0.3.9
4
4
  Summary: A downloader for DRM-protected & Non DRM-protected content.
5
5
  License: MIT License
6
6
 
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
4
4
 
5
5
  [project]
6
6
  name = "DDownloader"
7
- version = "0.3.8"
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.8"
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