media-downloader 2.1.2__py3-none-any.whl → 2.1.3__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.
Potentially problematic release.
This version of media-downloader might be problematic. Click here for more details.
- media_downloader/__init__.py +21 -16
- media_downloader/media_downloader_mcp.py +68 -29
- {media_downloader-2.1.2.dist-info → media_downloader-2.1.3.dist-info}/METADATA +3 -3
- media_downloader-2.1.3.dist-info/RECORD +11 -0
- media_downloader-2.1.2.dist-info/RECORD +0 -11
- {media_downloader-2.1.2.dist-info → media_downloader-2.1.3.dist-info}/WHEEL +0 -0
- {media_downloader-2.1.2.dist-info → media_downloader-2.1.3.dist-info}/entry_points.txt +0 -0
- {media_downloader-2.1.2.dist-info → media_downloader-2.1.3.dist-info}/licenses/LICENSE +0 -0
- {media_downloader-2.1.2.dist-info → media_downloader-2.1.3.dist-info}/top_level.txt +0 -0
media_downloader/__init__.py
CHANGED
|
@@ -1,24 +1,29 @@
|
|
|
1
1
|
#!/usr/bin/env python
|
|
2
2
|
# coding: utf-8
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
media_downloader_mcp,
|
|
11
|
-
|
|
4
|
+
import importlib
|
|
5
|
+
import inspect
|
|
6
|
+
|
|
7
|
+
# List of modules to import from
|
|
8
|
+
MODULES = [
|
|
9
|
+
'media_downloader.media_downloader',
|
|
10
|
+
'media_downloader.media_downloader_mcp',
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
# Initialize __all__ to expose all public classes and functions
|
|
14
|
+
__all__ = []
|
|
15
|
+
|
|
16
|
+
# Dynamically import all classes and functions from the specified modules
|
|
17
|
+
for module_name in MODULES:
|
|
18
|
+
module = importlib.import_module(module_name)
|
|
19
|
+
for name, obj in inspect.getmembers(module):
|
|
20
|
+
# Include only classes and functions, excluding private (starting with '_')
|
|
21
|
+
if (inspect.isclass(obj) or inspect.isfunction(obj)) and not name.startswith('_'):
|
|
22
|
+
globals()[name] = obj
|
|
23
|
+
__all__.append(name)
|
|
12
24
|
|
|
13
25
|
"""
|
|
14
26
|
media-downloader
|
|
15
27
|
|
|
16
28
|
Download videos and audio from the internet!
|
|
17
|
-
"""
|
|
18
|
-
|
|
19
|
-
__all__ = [
|
|
20
|
-
"media_downloader",
|
|
21
|
-
"media_downloader_mcp",
|
|
22
|
-
"setup_logging",
|
|
23
|
-
"MediaDownloader",
|
|
24
|
-
]
|
|
29
|
+
"""
|
|
@@ -4,7 +4,7 @@ import argparse
|
|
|
4
4
|
import os
|
|
5
5
|
import sys
|
|
6
6
|
import logging
|
|
7
|
-
from typing import Optional
|
|
7
|
+
from typing import Optional, Union, Dict, Any
|
|
8
8
|
from media_downloader import MediaDownloader, setup_logging
|
|
9
9
|
from fastmcp import FastMCP, Context
|
|
10
10
|
from pydantic import Field
|
|
@@ -15,14 +15,14 @@ setup_logging(is_mcp_server=True, log_file="media_downloader_mcp.log")
|
|
|
15
15
|
mcp = FastMCP(name="MediaDownloaderServer")
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
def to_boolean(string):
|
|
19
|
-
|
|
18
|
+
def to_boolean(string: Union[str, bool] = None) -> bool:
|
|
19
|
+
if isinstance(string, bool):
|
|
20
|
+
return string
|
|
21
|
+
if not string:
|
|
22
|
+
return False
|
|
20
23
|
normalized = str(string).strip().lower()
|
|
21
|
-
|
|
22
|
-
# Define valid true/false values
|
|
23
24
|
true_values = {"t", "true", "y", "yes", "1"}
|
|
24
25
|
false_values = {"f", "false", "n", "no", "0"}
|
|
25
|
-
|
|
26
26
|
if normalized in true_values:
|
|
27
27
|
return True
|
|
28
28
|
elif normalized in false_values:
|
|
@@ -31,6 +31,7 @@ def to_boolean(string):
|
|
|
31
31
|
raise ValueError(f"Cannot convert '{string}' to boolean")
|
|
32
32
|
|
|
33
33
|
|
|
34
|
+
|
|
34
35
|
@mcp.tool(
|
|
35
36
|
annotations={
|
|
36
37
|
"title": "Download Media",
|
|
@@ -42,22 +43,22 @@ def to_boolean(string):
|
|
|
42
43
|
tags={"collection_management"},
|
|
43
44
|
)
|
|
44
45
|
async def download_media(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
) -> str:
|
|
46
|
+
video_url: str = Field(description="Video URL to Download", default=None),
|
|
47
|
+
download_directory: Optional[str] = Field(
|
|
48
|
+
description="The directory where the media will be saved. If None, uses default directory.",
|
|
49
|
+
default=os.environ.get("DOWNLOAD_DIRECTORY", None),
|
|
50
|
+
),
|
|
51
|
+
audio_only: Optional[bool] = Field(
|
|
52
|
+
description="Downloads only the audio",
|
|
53
|
+
default=to_boolean(os.environ.get("AUDIO_ONLY", False)),
|
|
54
|
+
),
|
|
55
|
+
ctx: Context = Field(
|
|
56
|
+
description="MCP context for progress reporting.", default=None
|
|
57
|
+
),
|
|
58
|
+
) -> Dict[str, Any]:
|
|
58
59
|
"""
|
|
59
|
-
Downloads media from a given URL to the specified directory. Download as a video or audio file
|
|
60
|
-
Returns
|
|
60
|
+
Downloads media from a given URL to the specified directory. Download as a video or audio file.
|
|
61
|
+
Returns a Dictionary response with status, download directory, audio only, and other details.
|
|
61
62
|
"""
|
|
62
63
|
logger = logging.getLogger("MediaDownloader")
|
|
63
64
|
logger.debug(
|
|
@@ -66,9 +67,21 @@ async def download_media(
|
|
|
66
67
|
|
|
67
68
|
try:
|
|
68
69
|
if not video_url:
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
return {
|
|
71
|
+
"status": 400,
|
|
72
|
+
"message": "Invalid input: video_url must not be empty",
|
|
73
|
+
"data": {
|
|
74
|
+
"video_url": video_url,
|
|
75
|
+
"download_directory": download_directory,
|
|
76
|
+
"audio_only": audio_only
|
|
77
|
+
},
|
|
78
|
+
"error": "video_url must not be empty"
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if download_directory:
|
|
82
|
+
download_directory = os.path.expanduser(download_directory)
|
|
83
|
+
else:
|
|
84
|
+
download_directory = f'{os.path.expanduser("~")}/Downloads'
|
|
72
85
|
os.makedirs(download_directory, exist_ok=True)
|
|
73
86
|
|
|
74
87
|
downloader = MediaDownloader(
|
|
@@ -92,7 +105,16 @@ async def download_media(
|
|
|
92
105
|
file_path = downloader.download_video(link=video_url)
|
|
93
106
|
|
|
94
107
|
if not file_path or not os.path.exists(file_path):
|
|
95
|
-
|
|
108
|
+
return {
|
|
109
|
+
"status": 500,
|
|
110
|
+
"message": "Download failed: file not found",
|
|
111
|
+
"data": {
|
|
112
|
+
"video_url": video_url,
|
|
113
|
+
"download_directory": download_directory,
|
|
114
|
+
"audio_only": audio_only
|
|
115
|
+
},
|
|
116
|
+
"error": "Download failed or file not found"
|
|
117
|
+
}
|
|
96
118
|
|
|
97
119
|
# Report completion
|
|
98
120
|
if ctx:
|
|
@@ -100,15 +122,32 @@ async def download_media(
|
|
|
100
122
|
logger.debug("Reported final progress: 100/100")
|
|
101
123
|
|
|
102
124
|
logger.debug(f"Download completed, file path: {file_path}")
|
|
103
|
-
return
|
|
125
|
+
return {
|
|
126
|
+
"status": 200,
|
|
127
|
+
"message": "Media downloaded successfully",
|
|
128
|
+
"data": {
|
|
129
|
+
"file_path": file_path,
|
|
130
|
+
"download_directory": download_directory,
|
|
131
|
+
"audio_only": audio_only,
|
|
132
|
+
"video_url": video_url
|
|
133
|
+
}
|
|
134
|
+
}
|
|
104
135
|
except Exception as e:
|
|
105
|
-
logger.error(f"Failed to download media: {str(e)}")
|
|
106
|
-
|
|
136
|
+
logger.error(f"Failed to download media: {str(e)}\nParams: video_url: {video_url}, download directory: {download_directory}, audio only: {audio_only}")
|
|
137
|
+
return {
|
|
138
|
+
"status": 500,
|
|
139
|
+
"message": "Failed to download media",
|
|
140
|
+
"data": {
|
|
141
|
+
"video_url": video_url,
|
|
142
|
+
"download_directory": download_directory,
|
|
143
|
+
"audio_only": audio_only
|
|
144
|
+
},
|
|
145
|
+
"error": str(e)
|
|
146
|
+
}
|
|
107
147
|
|
|
108
148
|
|
|
109
149
|
def media_downloader_mcp():
|
|
110
150
|
parser = argparse.ArgumentParser(description="Run media downloader MCP server.")
|
|
111
|
-
|
|
112
151
|
parser.add_argument(
|
|
113
152
|
"-t",
|
|
114
153
|
"--transport",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: media-downloader
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.3
|
|
4
4
|
Summary: Download audio/videos from the internet!
|
|
5
5
|
Author-email: Audel Rouhi <knucklessg1@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -12,7 +12,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
12
12
|
Requires-Python: >=3.8
|
|
13
13
|
Description-Content-Type: text/markdown
|
|
14
14
|
License-File: LICENSE
|
|
15
|
-
Requires-Dist: yt-dlp>=2025.
|
|
15
|
+
Requires-Dist: yt-dlp>=2025.9.26
|
|
16
16
|
Requires-Dist: fastmcp>=2.11.3
|
|
17
17
|
Dynamic: license-file
|
|
18
18
|
|
|
@@ -38,7 +38,7 @@ Dynamic: license-file
|
|
|
38
38
|

|
|
39
39
|

|
|
40
40
|
|
|
41
|
-
*Version: 2.1.
|
|
41
|
+
*Version: 2.1.3*
|
|
42
42
|
|
|
43
43
|
Download videos and audio from the internet!
|
|
44
44
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
media_downloader/__init__.py,sha256=rnzjpKVjjq1kruHPnSoIdD_munB1olHMJA5onxM2dc0,798
|
|
2
|
+
media_downloader/__main__.py,sha256=5T-EOUX1ANJOd2ut4GoI9mvDpNCo25yZcOAZdnhzQI0,169
|
|
3
|
+
media_downloader/media_downloader.py,sha256=L20XUnnJ5UWq4ZZQIdmi7ZrOl2hXKSZkjNDH6l0eqKc,9097
|
|
4
|
+
media_downloader/media_downloader_mcp.py,sha256=8XPfTVOqjvkNY3V4stXDe5kGTA0OiWM-rOZLXQjIY0I,6378
|
|
5
|
+
media_downloader-2.1.3.dist-info/licenses/LICENSE,sha256=Z1xmcrPHBnGCETO_LLQJUeaSNBSnuptcDVTt4kaPUOE,1060
|
|
6
|
+
tests/test_mcp.py,sha256=mbe5O75w2SDr7-7nbzDSAHhMYx4qZKccU77h8Cp5LSo,1509
|
|
7
|
+
media_downloader-2.1.3.dist-info/METADATA,sha256=DDVpPSIvjd1Dj6fN-f_e6eg1Tl1cXsKW1bL5RL-fVFE,5979
|
|
8
|
+
media_downloader-2.1.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
9
|
+
media_downloader-2.1.3.dist-info/entry_points.txt,sha256=X_Ig7LdYJePZ9BQPMGyA2dSmBIQIYMHXCnTOeEbhgio,170
|
|
10
|
+
media_downloader-2.1.3.dist-info/top_level.txt,sha256=baD75VEFaytQEs4mmqyK1M0iZ5Y0HGF62vvtbSfbZCA,23
|
|
11
|
+
media_downloader-2.1.3.dist-info/RECORD,,
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
media_downloader/__init__.py,sha256=kSGgs6_eniQMrwlOcvtjwNeq5xbFl93KUciQpdY2NDw,414
|
|
2
|
-
media_downloader/__main__.py,sha256=5T-EOUX1ANJOd2ut4GoI9mvDpNCo25yZcOAZdnhzQI0,169
|
|
3
|
-
media_downloader/media_downloader.py,sha256=L20XUnnJ5UWq4ZZQIdmi7ZrOl2hXKSZkjNDH6l0eqKc,9097
|
|
4
|
-
media_downloader/media_downloader_mcp.py,sha256=IqfxtmIw12QTFwBtows4r7749baJfz8zoUZBRmhBarg,4820
|
|
5
|
-
media_downloader-2.1.2.dist-info/licenses/LICENSE,sha256=Z1xmcrPHBnGCETO_LLQJUeaSNBSnuptcDVTt4kaPUOE,1060
|
|
6
|
-
tests/test_mcp.py,sha256=mbe5O75w2SDr7-7nbzDSAHhMYx4qZKccU77h8Cp5LSo,1509
|
|
7
|
-
media_downloader-2.1.2.dist-info/METADATA,sha256=wNKKHXAlomUmAKR3kwfdkLckgj50N6TRHxeRKkrfItU,5979
|
|
8
|
-
media_downloader-2.1.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
9
|
-
media_downloader-2.1.2.dist-info/entry_points.txt,sha256=X_Ig7LdYJePZ9BQPMGyA2dSmBIQIYMHXCnTOeEbhgio,170
|
|
10
|
-
media_downloader-2.1.2.dist-info/top_level.txt,sha256=baD75VEFaytQEs4mmqyK1M0iZ5Y0HGF62vvtbSfbZCA,23
|
|
11
|
-
media_downloader-2.1.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|