DDownloader 0.3.2__tar.gz → 0.3.3__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.
Files changed (22) hide show
  1. ddownloader-0.3.3/DDownloader/main.py +120 -0
  2. ddownloader-0.3.3/DDownloader/modules/__init__.py +1 -0
  3. {ddownloader-0.3.2 → ddownloader-0.3.3}/DDownloader/modules/banners.py +27 -10
  4. ddownloader-0.3.2/DDownloader/modules/dash_downloader.py → ddownloader-0.3.3/DDownloader/modules/downloader.py +18 -21
  5. {ddownloader-0.3.2 → ddownloader-0.3.3}/DDownloader/modules/helper.py +62 -13
  6. {ddownloader-0.3.2 → ddownloader-0.3.3}/DDownloader/modules/streamlink.py +1 -1
  7. {ddownloader-0.3.2 → ddownloader-0.3.3}/DDownloader.egg-info/PKG-INFO +1 -7
  8. {ddownloader-0.3.2 → ddownloader-0.3.3}/DDownloader.egg-info/SOURCES.txt +1 -3
  9. {ddownloader-0.3.2 → ddownloader-0.3.3}/PKG-INFO +1 -7
  10. {ddownloader-0.3.2 → ddownloader-0.3.3}/pyproject.toml +20 -9
  11. ddownloader-0.3.2/DDownloader/main.py +0 -107
  12. ddownloader-0.3.2/DDownloader/modules/__init__.py +0 -1
  13. ddownloader-0.3.2/DDownloader/modules/hls_downloader.py +0 -99
  14. ddownloader-0.3.2/DDownloader.egg-info/requires.txt +0 -5
  15. {ddownloader-0.3.2 → ddownloader-0.3.3}/DDownloader/__init__.py +0 -0
  16. {ddownloader-0.3.2 → ddownloader-0.3.3}/DDownloader/modules/args_parser.py +0 -0
  17. {ddownloader-0.3.2 → ddownloader-0.3.3}/DDownloader.egg-info/dependency_links.txt +0 -0
  18. {ddownloader-0.3.2 → ddownloader-0.3.3}/DDownloader.egg-info/entry_points.txt +0 -0
  19. {ddownloader-0.3.2 → ddownloader-0.3.3}/DDownloader.egg-info/top_level.txt +0 -0
  20. {ddownloader-0.3.2 → ddownloader-0.3.3}/LICENSE +0 -0
  21. {ddownloader-0.3.2 → ddownloader-0.3.3}/README.md +0 -0
  22. {ddownloader-0.3.2 → ddownloader-0.3.3}/setup.cfg +0 -0
@@ -0,0 +1,120 @@
1
+ import os, re, logging, coloredlogs, time, json
2
+ from pathlib import Path
3
+ from colorama import Fore, Style
4
+ from DDownloader.modules.helper import download_binaries, detect_platform, get_media_info
5
+ from DDownloader.modules.args_parser import parse_arguments
6
+ from DDownloader.modules.banners import clear_and_print, display_help
7
+ from DDownloader.modules.downloader import DOWNLOADER
8
+
9
+ logger = logging.getLogger("+ MAIN + ")
10
+ coloredlogs.install(level='DEBUG', logger=logger)
11
+
12
+ # =========================================================================================================== #
13
+
14
+ def validate_directories():
15
+ downloads_dir = 'downloads'
16
+ if not os.path.exists(downloads_dir):
17
+ os.makedirs(downloads_dir)
18
+ logger.debug(f"Created '{downloads_dir}' directory.")
19
+ return downloads_dir
20
+
21
+ # =========================================================================================================== #
22
+
23
+ def process_media_info(directory="downloads", log_dir="logs"):
24
+ if not os.path.exists(log_dir):
25
+ os.makedirs(log_dir)
26
+ logger.info(f"Created logs directory: {log_dir}")
27
+
28
+ if not os.path.exists(directory):
29
+ logger.error(f"Directory '{directory}' does not exist. Please create it and add media files.")
30
+ return
31
+
32
+ mp4_files = [os.path.join(directory, f) for f in os.listdir(directory) if f.endswith(".mp4")]
33
+
34
+ if not mp4_files:
35
+ logger.info(f"No .mp4 files found in directory: {directory}")
36
+ return
37
+
38
+ logger.info(f"Found {len(mp4_files)} .mp4 file(s) in '{directory}'. Processing...")
39
+
40
+ for file_path in mp4_files:
41
+ try:
42
+ media_info = get_media_info(file_path)
43
+ if media_info:
44
+ base_name = os.path.splitext(os.path.basename(file_path))[0]
45
+ log_file_path = os.path.join(log_dir, f"{base_name}.log")
46
+ with open(log_file_path, "w", encoding="utf-8") as log_file:
47
+ json.dump(media_info, log_file, indent=4)
48
+ logger.info(f"Saved media information to: {log_file_path}")
49
+ print(Fore.RED + "═" * 80 + Fore.RESET + "\n")
50
+
51
+ except Exception as e:
52
+ logger.error(f"Failed to process {file_path}: {e}")
53
+
54
+ # =========================================================================================================== #
55
+
56
+ def main():
57
+ clear_and_print()
58
+ platform_name = detect_platform()
59
+ logger.info("Downloading binaries... Please wait!")
60
+ print(Fore.MAGENTA + "=" * 100 + Fore.RESET)
61
+ time.sleep(1)
62
+ bin_dir = Path(__file__).resolve().parent / "bin"
63
+ download_binaries(bin_dir, platform_name)
64
+ clear_and_print()
65
+
66
+ downloads_dir = validate_directories()
67
+ try:
68
+ args = parse_arguments()
69
+ except SystemExit:
70
+ display_help()
71
+ exit(1)
72
+
73
+ downloader = DOWNLOADER()
74
+
75
+ if re.search(r"\.mpd\b", args.url, re.IGNORECASE):
76
+ logger.info("DASH stream detected. Initializing DASH downloader...")
77
+ elif re.search(r"\.m3u8\b", args.url, re.IGNORECASE):
78
+ logger.info("HLS stream detected. Initializing HLS downloader...")
79
+ elif re.search(r"\.ism\b", args.url, re.IGNORECASE):
80
+ logger.info("ISM (Smooth Streaming) detected. Initializing ISM downloader...")
81
+ else:
82
+ logger.error("Unsupported URL format. Please provide a valid DASH (.mpd), HLS (.m3u8), or ISM (.ism) URL.")
83
+ exit(1)
84
+
85
+ downloader.manifest_url = args.url
86
+ downloader.output_name = args.output
87
+ downloader.decryption_keys = args.key or []
88
+ downloader.headers = args.header or []
89
+ downloader.proxy = args.proxy
90
+
91
+ if downloader.proxy:
92
+ if not downloader.proxy.startswith("http://"):
93
+ downloader.proxy = f"http://{downloader.proxy}"
94
+ logger.info(f"Proxy: {downloader.proxy}")
95
+ print(Fore.RED + "═" * 80 + Fore.RESET + "\n")
96
+
97
+ if downloader.headers:
98
+ logger.info("Headers:")
99
+ for header in downloader.headers:
100
+ logger.info(f" - {header}")
101
+ print(Fore.RED + "═" * 80 + Fore.RESET + "\n")
102
+
103
+ if downloader.decryption_keys:
104
+ logger.info("Decryption keys:")
105
+ for key in downloader.decryption_keys:
106
+ logger.info(f" - {key}")
107
+ print(Fore.RED + "═" * 80 + Fore.RESET + "\n")
108
+
109
+ try:
110
+ downloader.drm_downloader()
111
+ except Exception as e:
112
+ logger.error(f"An error occurred during the download process: {e}")
113
+ exit(1)
114
+
115
+ process_media_info(downloads_dir)
116
+
117
+ # =========================================================================================================== #
118
+
119
+ if __name__ == "__main__":
120
+ main()
@@ -0,0 +1 @@
1
+ __version__ = "0.3.3"
@@ -2,9 +2,13 @@ import os, time
2
2
  from sys import stdout
3
3
  from colorama import Fore, Style
4
4
 
5
+ # =========================================================================================================== #
6
+
5
7
  def clear_screen():
6
8
  os.system('cls' if os.name == 'nt' else 'clear')
7
-
9
+
10
+ # =========================================================================================================== #
11
+
8
12
  def banners():
9
13
  stdout.write(" \n")
10
14
  stdout.write(""+Fore.LIGHTRED_EX +"██████╗ ██████╗ ██████╗ ██╗ ██╗███╗ ██╗██╗ ██████╗ █████╗ ██████╗ █████╗ ███████╗██████╗ \n")
@@ -19,17 +23,30 @@ def banners():
19
23
  stdout.write(""+Fore.YELLOW +"╔════════════════════════════════════════════════════════════════════════════╝\n")
20
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")
21
25
  stdout.write(""+Fore.YELLOW +"╚════════════════════════════════════════════════════════════════════════════╝\n")
22
- print(f"{Fore.YELLOW}[DDownloader] - {Fore.GREEN}Download DASH or HLS streams with decryption keys. - {Fore.RED}[V0.3.2] \n{Fore.RESET}")
23
-
26
+ print(f"{Fore.YELLOW}[DDownloader] - {Fore.GREEN}Download DASH or HLS streams with decryption keys. - {Fore.RED}[V0.3.3] \n{Fore.RESET}")
27
+
28
+ # =========================================================================================================== #
29
+
24
30
  def clear_and_print():
25
31
  time.sleep(1)
26
32
  clear_screen()
27
33
  banners()
28
34
 
29
-
30
-
31
-
32
-
33
-
34
-
35
-
35
+ # =========================================================================================================== #
36
+
37
+ def display_help():
38
+ """Display custom help message with emoji."""
39
+ print(
40
+ f"{Fore.WHITE}+" + "=" * 100 + f"+{Style.RESET_ALL}\n"
41
+ f"{Fore.CYAN}{'Option':<40}{'Description':<90}{Style.RESET_ALL}\n"
42
+ f"{Fore.WHITE}+" + "=" * 100 + f"+{Style.RESET_ALL}\n"
43
+ f" {Fore.GREEN}-u, --url{' ' * 22}{Style.RESET_ALL}URL of the manifest (mpd/m3u8) 🌐\n"
44
+ f" {Fore.GREEN}-p, --proxy{' ' * 20}{Style.RESET_ALL}A proxy with protocol (http://ip:port) 🌍\n"
45
+ f" {Fore.GREEN}-o, --output{' ' * 19}{Style.RESET_ALL}Name of the output file 💾\n"
46
+ f" {Fore.GREEN}-k, --key{' ' * 22}{Style.RESET_ALL}Decryption key in KID:KEY format 🔑\n"
47
+ f" {Fore.GREEN}-H, --header{' ' * 19}{Style.RESET_ALL}Custom HTTP headers (e.g., User-Agent: value) 📋\n"
48
+ f" {Fore.GREEN}-h, --help{' ' * 21}{Style.RESET_ALL}Show this help message and exit ❓\n"
49
+ f"{Fore.WHITE}+" + "=" * 100 + f"+{Style.RESET_ALL}\n"
50
+ )
51
+
52
+ # =========================================================================================================== #
@@ -8,7 +8,7 @@ from colorama import Fore
8
8
  logger = logging.getLogger(Fore.RED + "+ DASH + ")
9
9
  coloredlogs.install(level='DEBUG', logger=logger)
10
10
 
11
- class DASH:
11
+ class DOWNLOADER:
12
12
  def __init__(self):
13
13
  self.manifest_url = None
14
14
  self.output_name = None
@@ -17,25 +17,20 @@ class DASH:
17
17
  self.headers = []
18
18
  self.binary_path = self._get_binary_path()
19
19
 
20
+ # =========================================================================================================== #
21
+
20
22
  def _get_binary_path(self):
21
- """
22
- Dynamically determine the path to the binary file in the 'bin' directory relative to the main module.
23
- """
24
- # Locate the base directory for the project (relative to main.py)
25
- base_dir = os.path.dirname(os.path.abspath(__file__)) # Directory containing the current module
26
- project_root = os.path.dirname(base_dir) # Go up one level to the project root
27
- bin_dir = os.path.join(project_root, 'bin') # Bin directory is under the project root
28
-
29
- # Determine the binary file name based on the platform
23
+ base_dir = os.path.dirname(os.path.abspath(__file__))
24
+ project_root = os.path.dirname(base_dir)
25
+ bin_dir = os.path.join(project_root, 'bin')
26
+
30
27
  binary_name = 'N_m3u8DL-RE.exe' if platform.system() == 'Windows' else 'N_m3u8DL-RE'
31
28
  binary = os.path.join(bin_dir, binary_name)
32
29
 
33
- # Check if the binary exists
34
30
  if not os.path.isfile(binary):
35
31
  logger.error(f"Binary not found: {binary}")
36
32
  raise FileNotFoundError(f"Binary not found: {binary}")
37
33
 
38
- # Ensure the binary is executable on Linux
39
34
  if platform.system() == 'Linux':
40
35
  chmod_command = ['chmod', '+x', binary]
41
36
  try:
@@ -44,16 +39,19 @@ class DASH:
44
39
  except subprocess.CalledProcessError as e:
45
40
  logger.error(Fore.RED + f"Failed to set executable permissions for: {binary}" + Fore.RESET)
46
41
  raise RuntimeError(f"Could not set executable permissions for: {binary}") from e
47
-
48
42
  return binary
49
43
 
50
- def dash_downloader(self):
44
+ # =========================================================================================================== #
45
+
46
+ def drm_downloader(self):
51
47
  if not self.manifest_url:
52
48
  logger.error("Manifest URL is not set.")
53
49
  return
54
50
  command = self._build_command()
55
51
  self._execute_command(command)
56
52
 
53
+ # =========================================================================================================== #
54
+
57
55
  def _build_command(self):
58
56
  command = [
59
57
  self.binary_path,
@@ -76,25 +74,24 @@ class DASH:
76
74
  self.proxy = f"http://{self.proxy}"
77
75
  command.extend(['--custom-proxy', f'"{self.proxy}"'])
78
76
 
79
- # Add headers if any are provided
80
77
  for header in self.headers:
81
78
  command.extend(['-H', f'"{header}"'])
82
79
 
83
- # logger.debug(f"Built command: {' '.join(command)}")
84
80
  return command
85
81
 
82
+ # =========================================================================================================== #
83
+
86
84
  def _execute_command(self, command):
87
85
  try:
88
86
  command_str = ' '.join(command)
89
- # logger.debug(f"Executing command: {command_str}")
90
87
  result = os.system(command_str)
91
88
 
92
89
  if result == 0:
93
90
  logger.info(Fore.GREEN + "Downloaded successfully. Bye!" + Fore.RESET)
94
91
  else:
95
- logger.info(Fore.GREEN + "Downloaded successfully. Bye!" + Fore.RESET)
96
- # logger.error(Fore.RED + f"Download failed with result code: {result}" + Fore.RESET)
97
- # logger.error(Fore.RED + f"Command: {command_str}" + Fore.RESET)
92
+ pass
93
+
98
94
  except Exception as e:
99
95
  logger.error(Fore.RED + f"An unexpected error occurred: {e}" + Fore.RESET)
100
-
96
+
97
+ # =========================================================================================================== #
@@ -5,15 +5,15 @@ from colorama import Fore, Style, init
5
5
  import logging
6
6
  import coloredlogs
7
7
  import platform
8
+ from pymediainfo import MediaInfo
8
9
 
9
- # Initialize Colorama for Windows compatibility
10
10
  init(autoreset=True)
11
11
 
12
- # Logger setup
13
12
  logger = logging.getLogger(Fore.GREEN + "+ HELPER + ")
14
13
  coloredlogs.install(level='DEBUG', logger=logger)
15
14
 
16
- # Binaries with platform-specific handling
15
+ # =========================================================================================================== #
16
+
17
17
  binaries = {
18
18
  "Windows": [
19
19
  "https://github.com/ThatNotEasy/DDownloader/raw/refs/heads/main/DDownloader/bin/N_m3u8DL-RE.exe",
@@ -29,10 +29,9 @@ binaries = {
29
29
  ]
30
30
  }
31
31
 
32
+ # =========================================================================================================== #
33
+
32
34
  def download_binaries(bin_dir, platform_name):
33
- """
34
- Downloads platform-specific binaries to the specified directory.
35
- """
36
35
  os.makedirs(bin_dir, exist_ok=True)
37
36
  logger.info(f"Platform detected: {platform_name}")
38
37
  logger.info(f"Using binary directory: {bin_dir}")
@@ -56,7 +55,6 @@ def download_binaries(bin_dir, platform_name):
56
55
  response = requests.get(binary_url, stream=True, timeout=30)
57
56
  response.raise_for_status()
58
57
 
59
- # Total size for progress bar
60
58
  total_size = int(response.headers.get('content-length', 0))
61
59
  with open(filepath, "wb") as file, tqdm(
62
60
  total=total_size,
@@ -70,8 +68,6 @@ def download_binaries(bin_dir, platform_name):
70
68
  file.write(chunk)
71
69
  progress_bar.update(len(chunk))
72
70
 
73
- # logger.info(f"{Fore.GREEN}Downloaded and saved: {filepath}{Fore.RESET}")
74
- # Make binary executable on Linux
75
71
  if platform_name == "Linux":
76
72
  os.chmod(filepath, 0o755)
77
73
  except requests.exceptions.RequestException as e:
@@ -79,10 +75,9 @@ def download_binaries(bin_dir, platform_name):
79
75
  except Exception as e:
80
76
  logger.error(f"{Fore.RED}Unexpected error for {binary_url}: {e}{Fore.RESET}")
81
77
 
78
+ # =========================================================================================================== #
79
+
82
80
  def detect_platform():
83
- """
84
- Detects the current operating system platform.
85
- """
86
81
  system_platform = platform.system().lower()
87
82
  if system_platform == 'windows':
88
83
  return 'Windows'
@@ -91,4 +86,58 @@ def detect_platform():
91
86
  elif system_platform == 'darwin':
92
87
  return 'MacOS'
93
88
  else:
94
- return 'Unknown'
89
+ return 'Unknown'
90
+
91
+ # =========================================================================================================== #
92
+
93
+ def get_media_info(file_path):
94
+ try:
95
+ logger.info(f"Parsing media file: {file_path}")
96
+ media_info = MediaInfo.parse(file_path)
97
+ result = {"file_path": file_path, "tracks": []}
98
+
99
+ for track in media_info.tracks:
100
+ track_info = {"track_type": track.track_type}
101
+
102
+ if track.track_type == "Video":
103
+ track_info.update({
104
+ "codec": track.codec,
105
+ "width": track.width,
106
+ "height": track.height,
107
+ "frame_rate": track.frame_rate,
108
+ "bit_rate": track.bit_rate,
109
+ "duration": track.duration,
110
+ "aspect_ratio": track.display_aspect_ratio,
111
+ })
112
+ elif track.track_type == "Audio":
113
+ track_info.update({
114
+ "codec": track.codec,
115
+ "channels": track.channel_s,
116
+ "sample_rate": track.sampling_rate,
117
+ "bit_rate": track.bit_rate,
118
+ "duration": track.duration,
119
+ "language": track.language,
120
+ })
121
+ elif track.track_type == "Text":
122
+ track_info.update({
123
+ "language": track.language,
124
+ "format": track.format,
125
+ })
126
+ elif track.track_type == "General":
127
+ track_info.update({
128
+ "file_size": track.file_size,
129
+ "format": track.format,
130
+ "duration": track.duration,
131
+ "overall_bit_rate": track.overall_bit_rate,
132
+ })
133
+
134
+ result["tracks"].append(track_info)
135
+
136
+ logger.info(f"Successfully extracted media information for: {file_path}")
137
+ return result
138
+
139
+ except Exception as e:
140
+ logger.error(f"Error occurred while parsing media file '{file_path}': {e}")
141
+ return None
142
+
143
+ # =========================================================================================================== #
@@ -16,4 +16,4 @@ class STREAMLINK:
16
16
  self.binary_path = os.path.join(os.path.dirname(__file__), 'bin', 'streamlink')
17
17
 
18
18
  def streamlink_restream(self):
19
- pass
19
+ pass
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: DDownloader
3
- Version: 0.3.2
3
+ Version: 0.3.3
4
4
  Summary: A downloader for DRM-protected content.
5
5
  Author-email: ThatNotEasy <apidotmy@proton.me>
6
6
  License: MIT License
@@ -24,7 +24,6 @@ License: MIT License
24
24
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25
25
  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
26
26
  IN THE SOFTWARE.
27
- Project-URL: homepage, https://github.com/ThatNotEasy/DDownloader
28
27
  Classifier: Programming Language :: Python :: 3
29
28
  Classifier: Programming Language :: Python :: 3.7
30
29
  Classifier: Programming Language :: Python :: 3.8
@@ -36,11 +35,6 @@ Classifier: Operating System :: OS Independent
36
35
  Requires-Python: >=3.7
37
36
  Description-Content-Type: text/markdown
38
37
  License-File: LICENSE
39
- Requires-Dist: requests>=2.26.0
40
- Requires-Dist: coloredlogs>=15.0
41
- Requires-Dist: tqdm>=4.64.0
42
- Requires-Dist: colorama>=0.4.5
43
- Requires-Dist: loguru>=0.6.0
44
38
 
45
39
  # DDownloader
46
40
  - DDownloader is a Python library to download HLS and DASH manifests and decrypt media files.
@@ -7,12 +7,10 @@ DDownloader.egg-info/PKG-INFO
7
7
  DDownloader.egg-info/SOURCES.txt
8
8
  DDownloader.egg-info/dependency_links.txt
9
9
  DDownloader.egg-info/entry_points.txt
10
- DDownloader.egg-info/requires.txt
11
10
  DDownloader.egg-info/top_level.txt
12
11
  DDownloader/modules/__init__.py
13
12
  DDownloader/modules/args_parser.py
14
13
  DDownloader/modules/banners.py
15
- DDownloader/modules/dash_downloader.py
14
+ DDownloader/modules/downloader.py
16
15
  DDownloader/modules/helper.py
17
- DDownloader/modules/hls_downloader.py
18
16
  DDownloader/modules/streamlink.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: DDownloader
3
- Version: 0.3.2
3
+ Version: 0.3.3
4
4
  Summary: A downloader for DRM-protected content.
5
5
  Author-email: ThatNotEasy <apidotmy@proton.me>
6
6
  License: MIT License
@@ -24,7 +24,6 @@ License: MIT License
24
24
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25
25
  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
26
26
  IN THE SOFTWARE.
27
- Project-URL: homepage, https://github.com/ThatNotEasy/DDownloader
28
27
  Classifier: Programming Language :: Python :: 3
29
28
  Classifier: Programming Language :: Python :: 3.7
30
29
  Classifier: Programming Language :: Python :: 3.8
@@ -36,11 +35,6 @@ Classifier: Operating System :: OS Independent
36
35
  Requires-Python: >=3.7
37
36
  Description-Content-Type: text/markdown
38
37
  License-File: LICENSE
39
- Requires-Dist: requests>=2.26.0
40
- Requires-Dist: coloredlogs>=15.0
41
- Requires-Dist: tqdm>=4.64.0
42
- Requires-Dist: colorama>=0.4.5
43
- Requires-Dist: loguru>=0.6.0
44
38
 
45
39
  # DDownloader
46
40
  - DDownloader is a Python library to download HLS and DASH manifests and decrypt media files.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "DDownloader"
7
- version = "0.3.2"
7
+ version = "0.3.3"
8
8
  description = "A downloader for DRM-protected content."
9
9
  readme = { file = "README.md", content-type = "text/markdown" }
10
10
  authors = [
@@ -22,19 +22,30 @@ classifiers = [
22
22
  "License :: OSI Approved :: MIT License",
23
23
  "Operating System :: OS Independent"
24
24
  ]
25
- dependencies = [
26
- "requests>=2.26.0",
27
- "coloredlogs>=15.0",
28
- "tqdm>=4.64.0",
29
- "colorama>=0.4.5",
30
- "loguru>=0.6.0"
31
- ]
32
25
 
33
- [project.urls]
26
+ [tool.poetry]
27
+ name = "DDownloader"
28
+ version = "0.3.3"
29
+ description = "A downloader for DRM-protected content."
30
+ authors = ["ThatNotEasy <apidotmy@proton.me>"]
31
+ license = "MIT"
32
+ readme = "README.md"
34
33
  homepage = "https://github.com/ThatNotEasy/DDownloader"
35
34
 
35
+ [tool.poetry.dependencies]
36
+ python = ">=3.7"
37
+ requests = ">=2.26.0"
38
+ coloredlogs = ">=15.0"
39
+ tqdm = ">=4.64.0"
40
+ colorama = ">=0.4.5"
41
+ loguru = ">=0.6.0"
42
+ pymediainfo = "^6.1.0" # Added pymediainfo
43
+
36
44
  [tool.setuptools.packages.find]
37
45
  include = ["DDownloader", "DDownloader.*"]
38
46
 
39
47
  [project.scripts]
40
48
  DDownloader = "DDownloader.main:main"
49
+
50
+ [tool.poetry.scripts]
51
+ DDownloader = "DDownloader.main:main"
@@ -1,107 +0,0 @@
1
- import os
2
- import re
3
- import logging
4
- import coloredlogs
5
- import time
6
- from pathlib import Path
7
- from colorama import Fore, Style
8
- from DDownloader.modules.helper import download_binaries, detect_platform
9
- from DDownloader.modules.args_parser import parse_arguments
10
- from DDownloader.modules.banners import clear_and_print
11
- from DDownloader.modules.dash_downloader import DASH
12
- from DDownloader.modules.hls_downloader import HLS
13
-
14
- # Setup logger
15
- logger = logging.getLogger("+ MAIN + ")
16
- coloredlogs.install(level='DEBUG', logger=logger)
17
-
18
- def validate_directories():
19
- downloads_dir = 'downloads'
20
- if not os.path.exists(downloads_dir):
21
- os.makedirs(downloads_dir)
22
- # logger.debug(f"Created '{downloads_dir}' directory.")
23
-
24
- def display_help():
25
- """Display custom help message with emoji."""
26
- print(
27
- f"{Fore.WHITE}+" + "=" * 100 + f"+{Style.RESET_ALL}\n"
28
- f"{Fore.CYAN}{'Option':<40}{'Description':<90}{Style.RESET_ALL}\n"
29
- f"{Fore.WHITE}+" + "=" * 100 + f"+{Style.RESET_ALL}\n"
30
- f" {Fore.GREEN}-u, --url{' ' * 22}{Style.RESET_ALL}URL of the manifest (mpd/m3u8) 🌐\n"
31
- f" {Fore.GREEN}-p, --proxy{' ' * 20}{Style.RESET_ALL}A proxy with protocol (http://ip:port) 🌍\n"
32
- f" {Fore.GREEN}-o, --output{' ' * 19}{Style.RESET_ALL}Name of the output file 💾\n"
33
- f" {Fore.GREEN}-k, --key{' ' * 22}{Style.RESET_ALL}Decryption key in KID:KEY format 🔑\n"
34
- f" {Fore.GREEN}-H, --header{' ' * 19}{Style.RESET_ALL}Custom HTTP headers (e.g., User-Agent: value) 📋\n"
35
- f" {Fore.GREEN}-h, --help{' ' * 21}{Style.RESET_ALL}Show this help message and exit ❓\n"
36
- f"{Fore.WHITE}+" + "=" * 100 + f"+{Style.RESET_ALL}\n"
37
- )
38
-
39
- def main():
40
- clear_and_print()
41
- platform_name = detect_platform()
42
- logger.info(f"Downloading binaries... Please wait!")
43
- print(Fore.MAGENTA + "=" * 100 + Fore.RESET)
44
- time.sleep(1)
45
- bin_dir = Path(__file__).resolve().parent / "bin"
46
- download_binaries(bin_dir, platform_name)
47
- clear_and_print()
48
-
49
- validate_directories()
50
- try:
51
- args = parse_arguments()
52
- except SystemExit:
53
- display_help()
54
- exit(1)
55
-
56
- downloader = None
57
- if re.search(r"\.mpd\b", args.url, re.IGNORECASE):
58
- logger.info("DASH stream detected. Initializing DASH downloader...")
59
- downloader = DASH()
60
- elif re.search(r"\.m3u8\b", args.url, re.IGNORECASE):
61
- logger.info("HLS stream detected. Initializing HLS downloader...")
62
- downloader = HLS()
63
- else:
64
- logger.error("Unsupported URL format. Please provide a valid DASH (.mpd) or HLS (.m3u8) URL.")
65
- exit(1)
66
-
67
- # Configure downloader
68
- downloader.manifest_url = args.url
69
- downloader.output_name = args.output
70
- downloader.decryption_keys = args.key or []
71
- downloader.headers = args.header or []
72
- downloader.proxy = args.proxy # Add proxy if provided
73
-
74
- if downloader.proxy:
75
- print(Fore.MAGENTA + "=" * 100 + Fore.RESET)
76
- if not downloader.proxy.startswith("http://"):
77
- downloader.proxy = f"http://{downloader.proxy}"
78
- logger.info(f"Proxy: {downloader.proxy}")
79
- print(Fore.MAGENTA + "=" * 100 + Fore.RESET)
80
-
81
- # Log provided headers
82
- if downloader.headers:
83
- print(Fore.MAGENTA + "=" * 100 + Fore.RESET)
84
- logger.info("Headers provided:")
85
- for header in downloader.headers:
86
- logger.info(f" -H {header}")
87
- print(Fore.MAGENTA + "=" * 100 + Fore.RESET)
88
-
89
- # Log provided decryption keys
90
- if downloader.decryption_keys:
91
- logger.info("Decryption keys provided:")
92
- for key in downloader.decryption_keys:
93
- logger.info(f" --key {key}")
94
- print(Fore.MAGENTA + "=" * 100 + Fore.RESET)
95
-
96
- # Execute downloader
97
- try:
98
- if isinstance(downloader, DASH):
99
- downloader.dash_downloader()
100
- elif isinstance(downloader, HLS):
101
- downloader.hls_downloader()
102
- except Exception as e:
103
- logger.error(f"An error occurred during the download process: {e}")
104
- exit(1)
105
-
106
- if __name__ == "__main__":
107
- main()
@@ -1,99 +0,0 @@
1
- import os
2
- import subprocess
3
- import logging
4
- import platform
5
- import coloredlogs
6
- from colorama import Fore
7
-
8
- logger = logging.getLogger(Fore.RED + "+ HLS + ")
9
- coloredlogs.install(level='DEBUG', logger=logger)
10
-
11
- class HLS:
12
- def __init__(self):
13
- self.manifest_url = None
14
- self.output_name = None
15
- self.proxy = None
16
- self.decryption_keys = []
17
- self.headers = []
18
- self.binary_path = self._get_binary_path()
19
-
20
- def _get_binary_path(self):
21
- """
22
- Dynamically determine the path to the binary file in the 'bin' directory relative to the main module.
23
- """
24
- # Locate the base directory for the project (relative to main.py)
25
- base_dir = os.path.dirname(os.path.abspath(__file__)) # Directory containing the current module
26
- project_root = os.path.dirname(base_dir) # Go up one level to the project root
27
- bin_dir = os.path.join(project_root, 'bin') # Bin directory is under the project root
28
-
29
- # Determine the binary file name based on the platform
30
- binary_name = 'N_m3u8DL-RE.exe' if platform.system() == 'Windows' else 'N_m3u8DL-RE'
31
- binary = os.path.join(bin_dir, binary_name)
32
-
33
- # Check if the binary exists
34
- if not os.path.isfile(binary):
35
- logger.error(f"Binary not found: {binary}")
36
- raise FileNotFoundError(f"Binary not found: {binary}")
37
-
38
- # Ensure the binary is executable on Linux
39
- if platform.system() == 'Linux':
40
- chmod_command = ['chmod', '+x', binary]
41
- try:
42
- subprocess.run(chmod_command, check=True)
43
- logger.info(Fore.CYAN + f"Set executable permission for: {binary}" + Fore.RESET)
44
- except subprocess.CalledProcessError as e:
45
- logger.error(Fore.RED + f"Failed to set executable permissions for: {binary}" + Fore.RESET)
46
- raise RuntimeError(f"Could not set executable permissions for: {binary}") from e
47
-
48
- return binary
49
-
50
- def hls_downloader(self):
51
- if not self.manifest_url:
52
- logger.error("Manifest URL is not set.")
53
- return
54
- command = self._build_command()
55
- self._execute_command(command)
56
-
57
- def _build_command(self):
58
- command = [
59
- self.binary_path,
60
- f'"{self.manifest_url}"',
61
- '--select-video', 'BEST',
62
- '--select-audio', 'BEST',
63
- '-mt',
64
- '-M', 'format=mp4',
65
- '--save-dir', '"downloads"',
66
- '--tmp-dir', '"downloads"',
67
- '--del-after-done',
68
- '--save-name', f'"{self.output_name}"'
69
- ]
70
-
71
- for key in self.decryption_keys:
72
- command.extend(['--key', f'"{key}"'])
73
-
74
- if self.proxy:
75
- if not self.proxy.startswith("http://"):
76
- self.proxy = f"http://{self.proxy}"
77
- command.extend(['--custom-proxy', f'"{self.proxy}"'])
78
-
79
- # Add headers if any are provided
80
- for header in self.headers:
81
- command.extend(['-H', f'"{header}"'])
82
-
83
- # logger.debug(f"Built command: {' '.join(command)}")
84
- return command
85
-
86
- def _execute_command(self, command):
87
- try:
88
- command_str = ' '.join(command)
89
- # logger.debug(f"Executing command: {command_str}")
90
- result = os.system(command_str)
91
-
92
- if result == 0:
93
- logger.info(Fore.GREEN + "Downloaded successfully. Bye!" + Fore.RESET)
94
- else:
95
- logger.info(Fore.GREEN + "Downloaded successfully. Bye!" + Fore.RESET)
96
- # logger.error(Fore.RED + f"Download failed with result code: {result}" + Fore.RESET)
97
- # logger.error(Fore.RED + f"Command: {command_str}" + Fore.RESET)
98
- except Exception as e:
99
- logger.error(Fore.RED + f"An unexpected error occurred: {e}" + Fore.RESET)
@@ -1,5 +0,0 @@
1
- requests>=2.26.0
2
- coloredlogs>=15.0
3
- tqdm>=4.64.0
4
- colorama>=0.4.5
5
- loguru>=0.6.0
File without changes
File without changes
File without changes