media-downloader 0.11.14__py2.py3-none-any.whl → 0.11.16__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.
@@ -51,10 +51,9 @@ class YtDlpLogger:
51
51
 
52
52
 
53
53
  class MediaDownloader:
54
-
55
54
  def __init__(
56
- self, links: list = [], download_directory: str = None, audio: bool = False
57
- ):
55
+ self, links: list = [], download_directory: str = None, audio: bool = False
56
+ ):
58
57
  self.links = links
59
58
  if download_directory:
60
59
  self.download_directory = download_directory
@@ -62,50 +61,10 @@ class MediaDownloader:
62
61
  self.download_directory = f'{os.path.expanduser("~")}/Downloads'
63
62
  self.audio = audio
64
63
  self.logger = logging.getLogger("MediaDownloader")
64
+ self.progress_callback = None # Store callback for progress updates
65
65
 
66
- def open_file(self, file):
67
- youtube_urls = open(file, "r")
68
- for url in youtube_urls:
69
- self.links.append(url)
70
- self.links = list(dict.fromkeys(self.links))
71
-
72
- def get_save_path(self) -> str:
73
- return self.download_directory
74
-
75
- def set_save_path(self, download_directory):
76
- self.download_directory = download_directory.replace(os.sep, "/")
77
- self.logger.debug(f"Set download directory to: {self.download_directory}")
78
-
79
- def reset_links(self):
80
- self.logger.debug("Resetting links")
81
- self.links = []
82
-
83
- def extend_links(self, urls):
84
- self.logger.debug(f"Extending links: {urls}")
85
- self.links.extend(urls)
86
- self.links = list(dict.fromkeys(self.links))
87
-
88
- def append_link(self, url):
89
- self.logger.debug(f"Appending link: {url}")
90
- self.links.append(url)
91
- self.links = list(dict.fromkeys(self.links))
92
-
93
- def get_links(self) -> List[str]:
94
- return self.links
95
-
96
- def set_audio(self, audio=False):
97
- self.audio = audio
98
- self.logger.debug(f"Audio mode set to: {audio}")
99
-
100
- def download_all(self):
101
- self.logger.debug(f"Downloading {len(self.links)} links")
102
- pool = Pool(processes=os.cpu_count())
103
- try:
104
- pool.map(self.download_video, self.links)
105
- finally:
106
- pool.close()
107
- pool.join()
108
- self.reset_links()
66
+ def set_progress_callback(self, callback):
67
+ self.progress_callback = callback
109
68
 
110
69
  def download_video(self, link):
111
70
  self.logger.debug(f"Downloading video: {link}")
@@ -125,10 +84,10 @@ class MediaDownloader:
125
84
  ydl_opts = {
126
85
  "format": "bestaudio/best" if self.audio else "best",
127
86
  "outtmpl": outtmpl,
128
- "quiet": True, # Suppress yt_dlp console output
129
- "no_warnings": True, # Suppress warnings
130
- "progress_with_newline": False, # Disable progress output
131
- "logger": YtDlpLogger(self.logger), # Use custom logger
87
+ "quiet": True,
88
+ "no_warnings": True,
89
+ "progress_hooks": [self.progress_hook], # Add progress hook
90
+ "logger": YtDlpLogger(self.logger),
132
91
  }
133
92
  if self.audio:
134
93
  ydl_opts["postprocessors"] = [
@@ -142,7 +101,7 @@ class MediaDownloader:
142
101
  try:
143
102
  with yt_dlp.YoutubeDL(ydl_opts) as ydl:
144
103
  info = ydl.extract_info(link, download=True)
145
- return ydl.prepare_filename(info) # Return the actual file path
104
+ return ydl.prepare_filename(info)
146
105
  except Exception as e:
147
106
  self.logger.error(f"Failed to download {link}: {str(e)}")
148
107
  try:
@@ -156,43 +115,18 @@ class MediaDownloader:
156
115
  return None
157
116
 
158
117
  def get_channel_videos(self, channel, limit=-1):
159
- self.logger.debug(f"Fetching videos for channel: {channel}, limit: {limit}")
160
- username = channel
161
- attempts = 0
162
- while attempts < 3:
163
- url = f"https://www.youtube.com/user/{username}/videos"
164
- self.logger.debug(f"Trying URL: {url}")
165
- page = requests.get(url).content
166
- data = str(page).split(" ")
167
- item = 'href="/watch?'
168
- vids = [
169
- line.replace('href="', "youtube.com") for line in data if item in line
170
- ]
171
- if vids:
172
- self.logger.debug(f"Found {len(vids)} videos")
173
- x = 0
174
- for vid in vids:
175
- if limit < 0 or x < limit:
176
- self.append_link(vid)
177
- x += 1
178
- return
179
- else:
180
- url = f"https://www.youtube.com/c/{channel}/videos"
118
+ self.logger.debug(f"Fetching videos for channel: {channel}, limit: {limit}")
119
+ username = channel
120
+ attempts = 0
121
+ while attempts < 3:
122
+ url = f"https://www.youtube.com/user/{username}/videos"
181
123
  self.logger.debug(f"Trying URL: {url}")
182
124
  page = requests.get(url).content
183
125
  data = str(page).split(" ")
184
- item = "https://i.ytimg.com/vi/"
185
- vids = []
186
- for line in data:
187
- if item in line:
188
- try:
189
- found = re.search(
190
- "https://i.ytimg.com/vi/(.+?)/hqdefault.", line
191
- ).group(1)
192
- vid = f"https://www.youtube.com/watch?v={found}"
193
- vids.append(vid)
194
- except AttributeError:
195
- continue
126
+ item = 'href="/watch?'
127
+ vids = [
128
+ line.replace('href="', "youtube.com") for line in data if item in line
129
+ ]
196
130
  if vids:
197
131
  self.logger.debug(f"Found {len(vids)} videos")
198
132
  x = 0
@@ -201,8 +135,59 @@ class MediaDownloader:
201
135
  self.append_link(vid)
202
136
  x += 1
203
137
  return
204
- attempts += 1
205
- self.logger.error(f"Could not find user or channel: {channel}")
138
+ else:
139
+ url = f"https://www.youtube.com/c/{channel}/videos"
140
+ self.logger.debug(f"Trying URL: {url}")
141
+ page = requests.get(url).content
142
+ data = str(page).split(" ")
143
+ item = "https://i.ytimg.com/vi/"
144
+ vids = []
145
+ for line in data:
146
+ if item in line:
147
+ try:
148
+ found = re.search(
149
+ "https://i.ytimg.com/vi/(.+?)/hqdefault.", line
150
+ ).group(1)
151
+ vid = f"https://www.youtube.com/watch?v={found}"
152
+ vids.append(vid)
153
+ except AttributeError:
154
+ continue
155
+ if vids:
156
+ self.logger.debug(f"Found {len(vids)} videos")
157
+ x = 0
158
+ for vid in vids:
159
+ if limit < 0 or x < limit:
160
+ self.append_link(vid)
161
+ x += 1
162
+ return
163
+ attempts += 1
164
+ self.logger.error(f"Could not find user or channel: {channel}")
165
+
166
+ def progress_hook(self, d):
167
+ if self.progress_callback and d["status"] == "downloading":
168
+ if d.get("total_bytes") and d.get("downloaded_bytes"):
169
+ progress = (d["downloaded_bytes"] / d["total_bytes"]) * 100
170
+ self.progress_callback(progress=progress, total=100)
171
+ elif d.get("downloaded_bytes"):
172
+ # Indeterminate progress if total_bytes is unavailable
173
+ self.progress_callback(progress=d["downloaded_bytes"])
174
+ elif d["status"] == "finished":
175
+ if self.progress_callback:
176
+ self.progress_callback(progress=100, total=100)
177
+
178
+ def download_all(self):
179
+ self.logger.debug(f"Downloading {len(self.links)} links")
180
+ pool = Pool(processes=os.cpu_count())
181
+ try:
182
+ results = pool.map(self.download_video, self.links)
183
+ self.reset_links()
184
+ for result in results:
185
+ if result and os.path.exists(result):
186
+ return result
187
+ return None
188
+ finally:
189
+ pool.close()
190
+ pool.join()
206
191
 
207
192
 
208
193
  def media_downloader(argv):
@@ -32,27 +32,40 @@ async def download_media(
32
32
  RuntimeError: If the download fails.
33
33
  """
34
34
  logger = logging.getLogger("MediaDownloader")
35
- logger.debug(
36
- f"Starting download for URL: {video_url}, directory: {download_directory}, audio_only: {audio_only}"
37
- )
35
+ logger.debug(f"Starting download for URL: {video_url}, directory: {download_directory}, audio_only: {audio_only}")
38
36
 
39
37
  try:
40
- # Validate inputs
41
38
  if not video_url or not download_directory:
42
39
  raise ValueError("video_url and download_directory must not be empty")
43
-
44
- # Ensure the download directory exists
45
40
  os.makedirs(download_directory, exist_ok=True)
46
41
 
47
- # Initialize MediaDownloader
48
42
  downloader = MediaDownloader(
49
43
  download_directory=download_directory, audio=audio_only
50
44
  )
45
+
46
+ # Set progress callback for yt_dlp
47
+ async def progress_callback(progress, total=None):
48
+ if ctx:
49
+ await ctx.report_progress(progress=progress, total=total)
50
+ logger.debug(f"Reported progress: {progress}/{total}")
51
+
52
+ downloader.set_progress_callback(progress_callback)
53
+
54
+ # Report initial progress
55
+ if ctx:
56
+ await ctx.report_progress(progress=0, total=100)
57
+ logger.debug("Reported initial progress: 0/100")
58
+
59
+ # Perform the download
51
60
  file_path = downloader.download_video(link=video_url)
52
61
 
53
- # Check if the file was downloaded
54
- # if not file_path:
55
- # raise RuntimeError("Download failed or file not found")
62
+ if not file_path or not os.path.exists(file_path):
63
+ raise RuntimeError("Download failed or file not found")
64
+
65
+ # Report completion
66
+ if ctx:
67
+ await ctx.report_progress(progress=100, total=100)
68
+ logger.debug("Reported final progress: 100/100")
56
69
 
57
70
  logger.debug(f"Download completed, file path: {file_path}")
58
71
  return file_path
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env python
2
2
  # coding: utf-8
3
3
 
4
- __version__ = "0.11.14"
4
+ __version__ = "0.11.16"
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.14
3
+ Version: 0.11.16
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
@@ -21,7 +21,7 @@ Classifier: Programming Language :: Python :: 3.14
21
21
  Description-Content-Type: text/markdown
22
22
  License-File: LICENSE
23
23
  Requires-Dist: fastmcp (>=2.11.3)
24
- Requires-Dist: yt-dlp (>=2023.12.30)
24
+ Requires-Dist: yt-dlp (>=2025.8.20)
25
25
 
26
26
  # Media Downloader
27
27
 
@@ -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.14*
48
+ *Version: 0.11.16*
49
49
 
50
50
  Download videos and audio from the internet!
51
51
 
@@ -0,0 +1,11 @@
1
+ media_downloader/__init__.py,sha256=d0PDULMlfmN1E52NKz20Wprllpq-UYMsBU3-2p4ZEk8,460
2
+ media_downloader/__main__.py,sha256=aHajZBE7fyiC7E5qjV5LMe9nCPkQV2YKu6FU8Ubkdm4,112
3
+ media_downloader/media_downloader.py,sha256=ufxRK8H2oc_bGxkewdLtfI8a3vkUI0NMJzCcIlc1NAg,9096
4
+ media_downloader/media_downloader_mcp.py,sha256=fULxaUQe3-YBveYvOP3eKpaWBa2nrKHaNtr02t4kGU0,3547
5
+ media_downloader/version.py,sha256=ZUpZiwSnYHH6PW485NRWDuEN5qgCB8keyTbrdCF9Rmw,118
6
+ media_downloader-0.11.16.dist-info/LICENSE,sha256=Z1xmcrPHBnGCETO_LLQJUeaSNBSnuptcDVTt4kaPUOE,1060
7
+ media_downloader-0.11.16.dist-info/METADATA,sha256=BsXpOE3APx7ksUZRK8WUAHVflXlp_5D1GLBtD1OcDrk,5522
8
+ media_downloader-0.11.16.dist-info/WHEEL,sha256=bb2Ot9scclHKMOLDEHY6B2sicWOgugjFKaJsT7vwMQo,110
9
+ media_downloader-0.11.16.dist-info/entry_points.txt,sha256=Hjp1vLkHPq_bABsuh4kpL5nddi1wn9Ftota-72_GgW4,142
10
+ media_downloader-0.11.16.dist-info/top_level.txt,sha256=B2OBmgONOm0hIyx2HJ8qFPOI_p5HOeolrYvmslVC1fc,17
11
+ media_downloader-0.11.16.dist-info/RECORD,,
@@ -1,11 +0,0 @@
1
- media_downloader/__init__.py,sha256=d0PDULMlfmN1E52NKz20Wprllpq-UYMsBU3-2p4ZEk8,460
2
- media_downloader/__main__.py,sha256=aHajZBE7fyiC7E5qjV5LMe9nCPkQV2YKu6FU8Ubkdm4,112
3
- media_downloader/media_downloader.py,sha256=IALP6PvypBjvtFbqHyG8FN3B7BONBJpNiz1vsJrPUoY,9139
4
- media_downloader/media_downloader_mcp.py,sha256=zFjamIgQznRQvRdF98XWgdDeWN3A59dyaugXnHxBZrI,2983
5
- media_downloader/version.py,sha256=lsEyJBBq_52dtz5_4fHeohUPAKr4Tf_LbF6XI2Xid5s,118
6
- media_downloader-0.11.14.dist-info/LICENSE,sha256=Z1xmcrPHBnGCETO_LLQJUeaSNBSnuptcDVTt4kaPUOE,1060
7
- media_downloader-0.11.14.dist-info/METADATA,sha256=FQ-gxkQ9yOqP-BXqwcZXZl0xDqkYq48elNYmFKPktHk,5523
8
- media_downloader-0.11.14.dist-info/WHEEL,sha256=bb2Ot9scclHKMOLDEHY6B2sicWOgugjFKaJsT7vwMQo,110
9
- media_downloader-0.11.14.dist-info/entry_points.txt,sha256=Hjp1vLkHPq_bABsuh4kpL5nddi1wn9Ftota-72_GgW4,142
10
- media_downloader-0.11.14.dist-info/top_level.txt,sha256=B2OBmgONOm0hIyx2HJ8qFPOI_p5HOeolrYvmslVC1fc,17
11
- media_downloader-0.11.14.dist-info/RECORD,,