media-downloader 0.11.10__py2.py3-none-any.whl → 0.11.12__py2.py3-none-any.whl

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.
@@ -2,7 +2,7 @@
2
2
  # coding: utf-8
3
3
 
4
4
  from media_downloader.version import __version__, __author__, __credits__
5
- from media_downloader.media_downloader import media_downloader, main, MediaDownloader
5
+ from media_downloader.media_downloader import media_downloader, main, setup_logging, MediaDownloader
6
6
 
7
7
  """
8
8
  media-downloader
@@ -14,4 +14,4 @@ __version__ = __version__
14
14
  __author__ = __author__
15
15
  __credits__ = __credits__
16
16
 
17
- __all__ = ["media_downloader", "main", "MediaDownloader"]
17
+ __all__ = ["media_downloader", "main", "setup_logging", "MediaDownloader"]
@@ -3,4 +3,4 @@
3
3
  from .media_downloader_mcp import main
4
4
 
5
5
  if __name__ == "__main__":
6
- main()
6
+ main()
@@ -6,21 +6,48 @@ import sys
6
6
  import re
7
7
  import getopt
8
8
  from typing import List
9
-
9
+ import logging
10
10
  import requests
11
11
  import yt_dlp
12
12
  from multiprocessing import Pool
13
13
 
14
14
 
15
- class StdOutLogger(object):
15
+ # Configure logging
16
+ def setup_logging(is_mcp_server=False, log_file="media_downloader.log"):
17
+ logger = logging.getLogger("MediaDownloader")
18
+ logger.setLevel(logging.DEBUG)
19
+
20
+ # Clear any existing handlers to avoid duplicate logs
21
+ logger.handlers.clear()
22
+
23
+ if is_mcp_server:
24
+ # Log to a file when running as MCP server
25
+ handler = logging.FileHandler(log_file)
26
+ else:
27
+ # Log to console (stdout) when running standalone
28
+ handler = logging.StreamHandler(sys.stdout)
29
+
30
+ handler.setLevel(logging.DEBUG)
31
+ formatter = logging.Formatter(
32
+ "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
33
+ )
34
+ handler.setFormatter(formatter)
35
+ logger.addHandler(handler)
36
+ return logger
37
+
38
+
39
+ class YtDlpLogger:
40
+ def __init__(self, logger):
41
+ self.logger = logger
42
+
16
43
  def debug(self, msg):
17
- print(f"{msg}")
44
+ self.logger.debug(msg)
18
45
 
19
46
  def warning(self, msg):
20
- print(f"{msg}")
47
+ self.logger.warning(msg)
21
48
 
22
49
  def error(self, msg):
23
- print(f"{msg}")
50
+ self.logger.error(msg)
24
51
 
25
52
 
26
53
  class MediaDownloader:
@@ -29,6 +56,7 @@ class MediaDownloader:
29
56
  self.links = []
30
57
  self.download_directory = f'{os.path.expanduser("~")}/Downloads'
31
58
  self.audio = False
59
+ self.logger = logging.getLogger("MediaDownloader")
32
60
 
33
61
  def open_file(self, file):
34
62
  youtube_urls = open(file, "r")
@@ -40,20 +68,20 @@ class MediaDownloader:
40
68
  return self.download_directory
41
69
 
42
70
  def set_save_path(self, download_directory):
43
- self.download_directory = download_directory
44
- self.download_directory = self.download_directory.replace(os.sep, "/")
71
+ self.download_directory = download_directory.replace(os.sep, "/")
72
+ self.logger.debug(f"Set download directory to: {self.download_directory}")
45
73
 
46
74
  def reset_links(self):
47
- print("Links Reset")
75
+ self.logger.debug("Resetting links")
48
76
  self.links = []
49
77
 
50
78
  def extend_links(self, urls):
51
- print("URL Extended: ", urls)
79
+ self.logger.debug(f"Extending links: {urls}")
52
80
  self.links.extend(urls)
53
81
  self.links = list(dict.fromkeys(self.links))
54
82
 
55
83
  def append_link(self, url):
56
- print("URL Appended: ", url)
84
+ self.logger.debug(f"Appending link: {url}")
57
85
  self.links.append(url)
58
86
  self.links = list(dict.fromkeys(self.links))
59
87
 
@@ -62,8 +90,10 @@ class MediaDownloader:
62
90
 
63
91
  def set_audio(self, audio=False):
64
92
  self.audio = audio
93
+ self.logger.debug(f"Audio mode set to: {audio}")
65
94
 
66
95
  def download_all(self):
96
+ self.logger.debug(f"Downloading {len(self.links)} links")
67
97
  pool = Pool(processes=os.cpu_count())
68
98
  try:
69
99
  pool.map(self.download_video, self.links)
@@ -73,8 +103,10 @@ class MediaDownloader:
73
103
  self.reset_links()
74
104
 
75
105
  def download_video(self, link):
106
+ self.logger.debug(f"Downloading video: {link}")
76
107
  outtmpl = f"{self.download_directory}/%(uploader)s - %(title)s.%(ext)s"
77
108
  if "rumble.com" in link:
109
+ self.logger.debug(f"Processing Rumble URL: {link}")
78
110
  rumble_url = requests.get(link)
79
111
  for rumble_embedded_url in rumble_url.text.split(","):
80
112
  if "embedUrl" in rumble_embedded_url:
@@ -83,141 +115,93 @@ class MediaDownloader:
83
115
  )
84
116
  link = rumble_embedded_url
85
117
  outtmpl = f"{self.download_directory}/%(title)s.%(ext)s"
118
+ self.logger.debug(f"Updated Rumble URL: {link}")
86
119
 
120
+ ydl_opts = {
121
+ "format": "bestaudio/best" if self.audio else "best",
122
+ "outtmpl": outtmpl,
123
+ "quiet": True, # Suppress yt_dlp console output
124
+ "no_warnings": True, # Suppress warnings
125
+ "progress_with_newline": False, # Disable progress output
126
+ "logger": YtDlpLogger(self.logger), # Use custom logger
127
+ }
87
128
  if self.audio:
88
- ydl_opts = {
89
- "format": "bestaudio/best",
90
- "postprocessors": [
91
- {
92
- "key": "FFmpegExtractAudio",
93
- "preferredcodec": "mp3",
94
- "preferredquality": "320",
95
- }
96
- ],
97
- "progress_with_newline": True,
98
- "logger": StdOutLogger(),
99
- "outtmpl": outtmpl,
100
- }
101
- else:
102
- ydl_opts = {
103
- "format": "best",
104
- "progress_with_newline": True,
105
- "logger": StdOutLogger(),
106
- "outtmpl": outtmpl,
107
- }
129
+ ydl_opts["postprocessors"] = [
130
+ {
131
+ "key": "FFmpegExtractAudio",
132
+ "preferredcodec": "mp3",
133
+ "preferredquality": "320",
134
+ }
135
+ ]
136
+
108
137
  try:
109
138
  with yt_dlp.YoutubeDL(ydl_opts) as ydl:
110
- print(ydl.download([link]))
111
- except Exception:
139
+ info = ydl.extract_info(link, download=True)
140
+ return ydl.prepare_filename(info) # Return the actual file path
141
+ except Exception as e:
142
+ self.logger.error(f"Failed to download {link}: {str(e)}")
112
143
  try:
113
- if self.audio:
114
- outtmpl = f"{self.download_directory}/%(id)s.%(ext)s"
115
- ydl_opts = {
116
- "format": "bestaudio/best",
117
- "progress_with_newline": True,
118
- "logger": StdOutLogger(),
119
- "postprocessors": [
120
- {
121
- "key": "FFmpegExtractAudio",
122
- "preferredcodec": "mp3",
123
- "preferredquality": "320",
124
- }
125
- ],
126
- "outtmpl": outtmpl,
127
- }
128
- else:
129
- ydl_opts = {
130
- "format": "best",
131
- "progress_with_newline": True,
132
- "logger": StdOutLogger(),
133
- "outtmpl": outtmpl,
134
- }
144
+ outtmpl = f"{self.download_directory}/%(id)s.%(ext)s"
145
+ ydl_opts["outtmpl"] = outtmpl
135
146
  with yt_dlp.YoutubeDL(ydl_opts) as ydl:
136
- print(ydl.download([link]))
137
- except Exception:
138
- print(f"Unable to download video: {link}")
147
+ info = ydl.extract_info(link, download=True)
148
+ return ydl.prepare_filename(info)
149
+ except Exception as e:
150
+ self.logger.error(f"Retry failed for {link}: {str(e)}")
151
+ return None
139
152
 
140
153
  def get_channel_videos(self, channel, limit=-1):
141
- vids = None
154
+ self.logger.debug(f"Fetching videos for channel: {channel}, limit: {limit}")
142
155
  username = channel
143
156
  attempts = 0
144
157
  while attempts < 3:
145
158
  url = f"https://www.youtube.com/user/{username}/videos"
159
+ self.logger.debug(f"Trying URL: {url}")
146
160
  page = requests.get(url).content
147
161
  data = str(page).split(" ")
148
162
  item = 'href="/watch?'
149
163
  vids = [
150
164
  line.replace('href="', "youtube.com") for line in data if item in line
151
- ] # list of all videos listed twice
152
- # print(vids) # index the latest video
153
- x = 0
165
+ ]
154
166
  if vids:
155
- # print("Link Set")
167
+ self.logger.debug(f"Found {len(vids)} videos")
168
+ x = 0
156
169
  for vid in vids:
157
- if limit < 0:
158
- self.links.append(vid)
159
- elif x >= limit:
160
- break
161
- else:
162
- self.links.append(vid)
170
+ if limit < 0 or x < limit:
171
+ self.append_link(vid)
163
172
  x += 1
173
+ return
164
174
  else:
165
175
  url = f"https://www.youtube.com/c/{channel}/videos"
166
- print("URL: ", url)
176
+ self.logger.debug(f"Trying URL: {url}")
167
177
  page = requests.get(url).content
168
- print("Page: ", page)
169
178
  data = str(page).split(" ")
170
- print("Data: ", data)
171
179
  item = "https://i.ytimg.com/vi/"
172
180
  vids = []
173
181
  for line in data:
174
182
  if item in line:
175
- vid = line
176
- # vid = line.replace('https://i.ytimg.com/vi/', '')
177
183
  try:
178
184
  found = re.search(
179
- "https://i.ytimg.com/vi/(.+?)/hqdefault.", vid
185
+ "https://i.ytimg.com/vi/(.+?)/hqdefault.", line
180
186
  ).group(1)
187
+ vid = f"https://www.youtube.com/watch?v={found}"
188
+ vids.append(vid)
181
189
  except AttributeError:
182
- # AAA, ZZZ not found in the original string
183
- found = "" # apply your error handling
184
- print("Vid, ", vid)
185
- vid = f"https://www.youtube.com/watch?v={found}"
186
- vids.append(vid)
187
- print(vids) # index the latest video
188
- x = 0
190
+ continue
189
191
  if vids:
190
- # print("Link Set")
192
+ self.logger.debug(f"Found {len(vids)} videos")
193
+ x = 0
191
194
  for vid in vids:
192
- if limit < 0:
193
- self.links.append(vid)
194
- elif x >= limit:
195
- break
196
- else:
197
- self.links.append(vid)
195
+ if limit < 0 or x < limit:
196
+ self.append_link(vid)
198
197
  x += 1
199
- else:
200
- print("Trying Old Method")
201
- vids = [
202
- line.replace('href="', "youtube.com")
203
- for line in data
204
- if item in line
205
- ] # list of all videos listed twice
206
- if vids:
207
- for vid in vids:
208
- if limit < 0:
209
- self.links.append(vid)
210
- elif x >= limit:
211
- break
212
- else:
213
- self.links.append(vid)
214
- x += 1
215
- else:
216
- print("Could not find User or Channel")
198
+ return
217
199
  attempts += 1
200
+ self.logger.error(f"Could not find user or channel: {channel}")
218
201
 
219
202
 
220
203
  def media_downloader(argv):
204
+ logger = setup_logging(is_mcp_server=False)
221
205
  video_downloader_instance = MediaDownloader()
222
206
  try:
223
207
  opts, args = getopt.getopt(
@@ -227,6 +211,7 @@ def media_downloader(argv):
227
211
  )
228
212
  except getopt.GetoptError:
229
213
  usage()
214
+ logger.error("Incorrect arguments")
230
215
  sys.exit(2)
231
216
  for opt, arg in opts:
232
217
  if opt in ("-h", "--help"):
@@ -241,8 +226,7 @@ def media_downloader(argv):
241
226
  elif opt in ("-f", "--file"):
242
227
  video_downloader_instance.open_file(arg)
243
228
  elif opt in ("-l", "--links"):
244
- url_list = arg.replace(" ", "")
245
- url_list = url_list.split(",")
229
+ url_list = arg.replace(" ", "").split(",")
246
230
  for url in url_list:
247
231
  video_downloader_instance.append_link(url)
248
232
 
@@ -251,16 +235,16 @@ def media_downloader(argv):
251
235
 
252
236
  def usage():
253
237
  print(
254
- f"Media-Downloader: A tool to download any video off the internet!\n"
255
- f"\nUsage:\n"
256
- f"-h | --help [ See usage ]\n"
257
- f"-a | --audio [ Download audio only ]\n"
258
- f"-c | --channel [ YouTube Channel/User - Downloads all videos ]\n"
259
- f"-d | --directory [ Location where the images will be saved ]\n"
260
- f"-f | --file [ Text file to read the URLs from ]\n"
261
- f"-l | --links [ Comma separated URLs (No spaces) ]\n"
262
- f"\nExample:\n"
263
- f'media-downloader -f "file_of_urls.txt" -l "URL1,URL2,URL3" -c "WhiteHouse" -d "~/Downloads"\n'
238
+ "Media-Downloader: A tool to download any video off the internet!\n"
239
+ "\nUsage:\n"
240
+ "-h | --help [ See usage ]\n"
241
+ "-a | --audio [ Download audio only ]\n"
242
+ "-c | --channel [ YouTube Channel/User - Downloads all videos ]\n"
243
+ "-d | --directory [ Location where the images will be saved ]\n"
244
+ "-f | --file [ Text file to read the URLs from ]\n"
245
+ "-l | --links [ Comma separated URLs (No spaces) ]\n"
246
+ "\nExample:\n"
247
+ 'media-downloader -f "file_of_urls.txt" -l "URL1,URL2,URL3" -c "WhiteHouse" -d "~/Downloads"\n'
264
248
  )
265
249
 
266
250
 
@@ -3,12 +3,14 @@
3
3
  import getopt
4
4
  import os
5
5
  import sys
6
- from media_downloader import MediaDownloader
6
+ import logging
7
+ from media_downloader import MediaDownloader, setup_logging
7
8
  from fastmcp import FastMCP
8
9
 
9
- mcp = FastMCP(
10
- name="MediaDownloaderServer",
11
- )
10
+ # Initialize logging for MCP server (logs to file)
11
+ setup_logging(is_mcp_server=True, log_file="media_downloader_mcp.log")
12
+
13
+ mcp = FastMCP(name="MediaDownloaderServer")
12
14
 
13
15
 
14
16
  @mcp.tool()
@@ -29,6 +31,11 @@ async def download_media(
29
31
  ValueError: If the URL or directory is invalid.
30
32
  RuntimeError: If the download fails.
31
33
  """
34
+ logger = logging.getLogger("MediaDownloader")
35
+ logger.debug(
36
+ f"Starting download for URL: {video_url}, directory: {download_directory}, audio_only: {audio_only}"
37
+ )
38
+
32
39
  try:
33
40
  # Validate inputs
34
41
  if not video_url or not download_directory:
@@ -44,20 +51,19 @@ async def download_media(
44
51
  downloader.append_link(video_url)
45
52
 
46
53
  # Perform the download
47
- downloader.download_all()
54
+ file_path = downloader.download_all()
48
55
 
49
- # Assume download_all() saves the file and the path can be retrieved
50
- # Adjust this based on actual MediaDownloader behavior
51
- save_path = os.path.join(download_directory, video_url.split("/")[-1])
52
- if not os.path.exists(save_path):
56
+ # Check if the file was downloaded
57
+ if not file_path or not os.path.exists(file_path):
53
58
  raise RuntimeError("Download failed or file not found")
54
59
 
55
- return download_directory
60
+ logger.debug(f"Download completed, file path: {file_path}")
61
+ return file_path
56
62
  except Exception as e:
63
+ logger.error(f"Failed to download media: {str(e)}")
57
64
  raise RuntimeError(f"Failed to download media: {str(e)}")
58
65
 
59
66
 
60
-
61
67
  def media_downloader_mcp(argv):
62
68
  transport = "stdio"
63
69
  host = "0.0.0.0"
@@ -84,25 +90,10 @@ def media_downloader_mcp(argv):
84
90
  elif transport == "http":
85
91
  mcp.run(transport="http", host=host, port=port)
86
92
  else:
87
- print("Transport not supported")
93
+ logger = logging.getLogger("MediaDownloader")
94
+ logger.error("Transport not supported")
88
95
  sys.exit(1)
89
96
 
90
- def client():
91
- # Connect to the server (update host/port if using http)
92
- client = MCPClient(host="localhost", port=5000)
93
-
94
- # Call the download_media tool
95
- response = client.call_tool(
96
- "download_media",
97
- {
98
- "video_url": "https://example.com/video.mp4",
99
- "download_directory": "./downloads",
100
- "audio_only": False,
101
- },
102
- )
103
-
104
- print(f"Downloaded file path: {response}")
105
-
106
97
 
107
98
  def main():
108
99
  mcp.run(transport="stdio")
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env python
2
2
  # coding: utf-8
3
3
 
4
- __version__ = "0.11.10"
4
+ __version__ = "0.11.12"
5
5
  __author__ = "Audel Rouhi"
6
6
  __credits__ = "Audel Rouhi"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: media-downloader
3
- Version: 0.11.10
3
+ Version: 0.11.12
4
4
  Summary: Download audio/videos from the internet!
5
5
  Home-page: https://github.com/Knuckles-Team/media-downloader
6
6
  Author: Audel Rouhi
@@ -45,7 +45,7 @@ Requires-Dist: yt-dlp (>=2023.12.30)
45
45
  ![PyPI - Wheel](https://img.shields.io/pypi/wheel/media-downloader)
46
46
  ![PyPI - Implementation](https://img.shields.io/pypi/implementation/media-downloader)
47
47
 
48
- *Version: 0.11.10*
48
+ *Version: 0.11.12*
49
49
 
50
50
  Download videos and audio from the internet!
51
51
 
@@ -0,0 +1,11 @@
1
+ media_downloader/__init__.py,sha256=chpniZ_Nq-mvyeI2M9bgYjqj1MyDUUfLbC4qxelfjpo,439
2
+ media_downloader/__main__.py,sha256=aHajZBE7fyiC7E5qjV5LMe9nCPkQV2YKu6FU8Ubkdm4,112
3
+ media_downloader/media_downloader.py,sha256=UMrwOgsRGW7Ltlo_oFxmrSFdVNcaiwb8Gg9ZqvHq6vQ,8945
4
+ media_downloader/media_downloader_mcp.py,sha256=rjBix4QNWi8PgEpYqNCfBXjyo8Om3qvzEgfS6MnxIMY,3093
5
+ media_downloader/version.py,sha256=TIpNgIHp1tmYNCzhNKUqEBEsDbI5W8Jsz7Uktt0FE6s,118
6
+ media_downloader-0.11.12.dist-info/LICENSE,sha256=Z1xmcrPHBnGCETO_LLQJUeaSNBSnuptcDVTt4kaPUOE,1060
7
+ media_downloader-0.11.12.dist-info/METADATA,sha256=oEX53GKRTgztjLBqnLdObZAHQd9TuVByhu-dCYW7eQw,5523
8
+ media_downloader-0.11.12.dist-info/WHEEL,sha256=bb2Ot9scclHKMOLDEHY6B2sicWOgugjFKaJsT7vwMQo,110
9
+ media_downloader-0.11.12.dist-info/entry_points.txt,sha256=Hjp1vLkHPq_bABsuh4kpL5nddi1wn9Ftota-72_GgW4,142
10
+ media_downloader-0.11.12.dist-info/top_level.txt,sha256=B2OBmgONOm0hIyx2HJ8qFPOI_p5HOeolrYvmslVC1fc,17
11
+ media_downloader-0.11.12.dist-info/RECORD,,
@@ -1,11 +0,0 @@
1
- media_downloader/__init__.py,sha256=NdFVoFJM5ltTmSM7Be2Hfx7u8CHFwdYYw2zciU1Z-Qw,407
2
- media_downloader/__main__.py,sha256=fOqZ8q9tcEZ44aLwuJsm6unhy8mRsKUdL4kwUtWU_Z0,111
3
- media_downloader/media_downloader.py,sha256=iZZXW3UKcu1vqJcKbGBQ1fP5jL1TcGQ5GIbKwNc10ms,9390
4
- media_downloader/media_downloader_mcp.py,sha256=NBXK8fkzDePO3FH7uraTOUgPp1nDdGc8xli0e9GJG9Y,3169
5
- media_downloader/version.py,sha256=lG0PXW5EMPLJYlkDY95xqDCnWUI7vp_A9SkszBzRSPM,118
6
- media_downloader-0.11.10.dist-info/LICENSE,sha256=Z1xmcrPHBnGCETO_LLQJUeaSNBSnuptcDVTt4kaPUOE,1060
7
- media_downloader-0.11.10.dist-info/METADATA,sha256=uvpwg0lnz9rSSvZ8ynJfADhGdq1Px4Pj8p-qWtfa20Y,5523
8
- media_downloader-0.11.10.dist-info/WHEEL,sha256=bb2Ot9scclHKMOLDEHY6B2sicWOgugjFKaJsT7vwMQo,110
9
- media_downloader-0.11.10.dist-info/entry_points.txt,sha256=Hjp1vLkHPq_bABsuh4kpL5nddi1wn9Ftota-72_GgW4,142
10
- media_downloader-0.11.10.dist-info/top_level.txt,sha256=B2OBmgONOm0hIyx2HJ8qFPOI_p5HOeolrYvmslVC1fc,17
11
- media_downloader-0.11.10.dist-info/RECORD,,