mcp-server-youtube-info 0.1.7__py3-none-any.whl → 0.1.8__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.
- mcp_server_youtube_info/server.py +25 -33
- mcp_server_youtube_info/util.py +154 -0
- {mcp_server_youtube_info-0.1.7.dist-info → mcp_server_youtube_info-0.1.8.dist-info}/METADATA +1 -1
- mcp_server_youtube_info-0.1.8.dist-info/RECORD +8 -0
- mcp_server_youtube_info-0.1.7.dist-info/RECORD +0 -7
- {mcp_server_youtube_info-0.1.7.dist-info → mcp_server_youtube_info-0.1.8.dist-info}/WHEEL +0 -0
- {mcp_server_youtube_info-0.1.7.dist-info → mcp_server_youtube_info-0.1.8.dist-info}/entry_points.txt +0 -0
@@ -1,33 +1,10 @@
|
|
1
1
|
from fastmcp import FastMCP, Image
|
2
|
-
import yt_dlp
|
3
2
|
import io
|
4
3
|
import httpx
|
5
4
|
from PIL import Image as PILImage
|
6
|
-
|
5
|
+
from mcp_server_youtube_info.util import VideoInfoExtractor
|
7
6
|
mcp = FastMCP("MCP YouTube Info Server", dependencies=["httpx", "Pillow"])
|
8
7
|
|
9
|
-
def _extract_info(video_id: str) -> dict:
|
10
|
-
"""Retrieve meta info of YouTube video using yt-dlp.
|
11
|
-
|
12
|
-
Args:
|
13
|
-
video_id (str): YouTube video ID
|
14
|
-
|
15
|
-
Returns:
|
16
|
-
dict: Video meta information
|
17
|
-
"""
|
18
|
-
ydl_opts = {
|
19
|
-
'quiet': True,
|
20
|
-
'no_warnings': True,
|
21
|
-
'extract_flat': True,
|
22
|
-
}
|
23
|
-
|
24
|
-
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
25
|
-
try:
|
26
|
-
url = f"https://www.youtube.com/watch?v={video_id}"
|
27
|
-
info = ydl.extract_info(url, download=False)
|
28
|
-
return info
|
29
|
-
except Exception as e:
|
30
|
-
raise Exception(f"Failed to retrieve video information: {str(e)}")
|
31
8
|
|
32
9
|
@mcp.tool()
|
33
10
|
def youtube_metainfo(video_id: str) -> dict:
|
@@ -40,10 +17,17 @@ def youtube_metainfo(video_id: str) -> dict:
|
|
40
17
|
dict: Video meta information
|
41
18
|
"""
|
42
19
|
try:
|
43
|
-
|
20
|
+
# Create extractor
|
21
|
+
extractor = VideoInfoExtractor()
|
22
|
+
|
23
|
+
# Get video information
|
24
|
+
raw_info = extractor.get_video_info(video_id)
|
25
|
+
|
26
|
+
return extractor.format_info(raw_info)
|
44
27
|
except Exception as e:
|
45
28
|
raise Exception(f"Failed to retrieve metadata: {str(e)}")
|
46
29
|
|
30
|
+
|
47
31
|
@mcp.tool()
|
48
32
|
def youtube_thumbnail_url(video_id: str) -> str:
|
49
33
|
"""Retrieve the thumbnail URL of a YouTube video.
|
@@ -55,41 +39,49 @@ def youtube_thumbnail_url(video_id: str) -> str:
|
|
55
39
|
str: Thumbnail URL
|
56
40
|
"""
|
57
41
|
try:
|
58
|
-
|
59
|
-
|
42
|
+
# Create extractor
|
43
|
+
extractor = VideoInfoExtractor()
|
44
|
+
# Get video information
|
45
|
+
raw_info = extractor.get_video_info(video_id)
|
46
|
+
thumbnail_url = raw_info.get('thumbnail')
|
60
47
|
if not thumbnail_url:
|
61
48
|
raise Exception("Cannot find thumbnail URL")
|
62
49
|
return thumbnail_url
|
63
50
|
except Exception as e:
|
64
51
|
raise Exception(f"Failed to get thumbnail URL: {str(e)}")
|
65
52
|
|
53
|
+
|
66
54
|
@mcp.tool()
|
67
55
|
def youtube_thumbnail_image(video_id: str) -> Image:
|
68
56
|
"""
|
69
57
|
Retrieve and download the thumbnail of a YouTube video as an Image.
|
70
|
-
|
58
|
+
|
71
59
|
Args:
|
72
60
|
video_id: YouTube Video ID
|
73
61
|
Returns:
|
74
62
|
Image: Image object containing the thumbnail data
|
75
63
|
"""
|
76
64
|
try:
|
77
|
-
|
78
|
-
|
65
|
+
# Create extractor
|
66
|
+
extractor = VideoInfoExtractor()
|
67
|
+
|
68
|
+
# Get video information
|
69
|
+
raw_info = extractor.get_video_info(video_id)
|
70
|
+
thumbnail_url = raw_info.get('thumbnail')
|
79
71
|
if not thumbnail_url:
|
80
72
|
raise Exception("Cannot find thumbnail URL")
|
81
73
|
except Exception as e:
|
82
74
|
raise Exception(f"Failed to get thumbnail URL: {str(e)}")
|
83
|
-
|
75
|
+
|
84
76
|
try:
|
85
77
|
with httpx.Client() as client:
|
86
78
|
response = client.get(thumbnail_url)
|
87
79
|
response.raise_for_status()
|
88
|
-
|
80
|
+
|
89
81
|
image = PILImage.open(io.BytesIO(response.content)).convert('RGB')
|
90
82
|
buffer = io.BytesIO()
|
91
83
|
image.save(buffer, format="JPEG", quality=60, optimize=True)
|
92
|
-
|
84
|
+
|
93
85
|
return Image(data=buffer.getvalue(), format="jpeg")
|
94
86
|
except httpx.HTTPStatusError as e:
|
95
87
|
raise Exception(f"HTTP Error: {e.response.status_code}")
|
@@ -0,0 +1,154 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
YouTube Video Metadata Retrieval Sample
|
4
|
+
|
5
|
+
This script is a sample for retrieving metadata of YouTube videos.
|
6
|
+
It only retrieves information without actual downloading.
|
7
|
+
"""
|
8
|
+
|
9
|
+
import sys
|
10
|
+
from typing import Dict, Any, Optional
|
11
|
+
import yt_dlp
|
12
|
+
|
13
|
+
|
14
|
+
class VideoInfoExtractor:
|
15
|
+
"""Class for retrieving YouTube video metadata"""
|
16
|
+
|
17
|
+
def __init__(self):
|
18
|
+
# yt-dlp settings (no download configuration)
|
19
|
+
self.ydl_opts = {
|
20
|
+
'quiet': True, # Suppress logs
|
21
|
+
'no_warnings': False, # Show warnings
|
22
|
+
'extract_flat': False, # Get detailed information
|
23
|
+
'simulate': True, # Simulation mode (no download)
|
24
|
+
'skip_download': True, # Skip download
|
25
|
+
}
|
26
|
+
|
27
|
+
def get_video_info(self, video_id: str) -> Optional[Dict[str, Any]]:
|
28
|
+
"""
|
29
|
+
Retrieve video information for the specified video ID
|
30
|
+
|
31
|
+
Args:
|
32
|
+
video_id (str): YouTube video ID
|
33
|
+
|
34
|
+
Returns:
|
35
|
+
Dict[str, Any]: Video information dictionary, None if error occurs
|
36
|
+
"""
|
37
|
+
try:
|
38
|
+
with yt_dlp.YoutubeDL(self.ydl_opts) as ydl:
|
39
|
+
# Get video information
|
40
|
+
url = f"https://www.youtube.com/watch?v={video_id}"
|
41
|
+
info = ydl.extract_info(url, download=False)
|
42
|
+
return info
|
43
|
+
except Exception as e:
|
44
|
+
print(f"An error occurred: {e}", file=sys.stderr)
|
45
|
+
return None
|
46
|
+
|
47
|
+
def format_info(self, info: Dict[str, Any]) -> Dict[str, Any]:
|
48
|
+
"""
|
49
|
+
Format and return the retrieved information
|
50
|
+
|
51
|
+
Args:
|
52
|
+
info (Dict[str, Any]): Raw information retrieved from yt-dlp
|
53
|
+
|
54
|
+
Returns:
|
55
|
+
Dict[str, Any]: Formatted information
|
56
|
+
"""
|
57
|
+
if not info:
|
58
|
+
return {}
|
59
|
+
|
60
|
+
# Extract basic information
|
61
|
+
formatted_info = {
|
62
|
+
'title': info.get('title', 'N/A'),
|
63
|
+
'uploader': info.get('uploader', 'N/A'),
|
64
|
+
'upload_date': info.get('upload_date', 'N/A'),
|
65
|
+
'duration': info.get('duration', 0),
|
66
|
+
'view_count': info.get('view_count', 0),
|
67
|
+
'like_count': info.get('like_count', 0),
|
68
|
+
'description': info.get('description', 'N/A')[:200] + '...' if info.get('description') else 'N/A',
|
69
|
+
'tags': info.get('tags', []),
|
70
|
+
'categories': info.get('categories', []),
|
71
|
+
'webpage_url': info.get('webpage_url', 'N/A'),
|
72
|
+
'thumbnail': info.get('thumbnail', 'N/A'),
|
73
|
+
}
|
74
|
+
|
75
|
+
# Available format information
|
76
|
+
formats = info.get('formats', [])
|
77
|
+
if formats:
|
78
|
+
# Summarize video format information concisely
|
79
|
+
video_formats = []
|
80
|
+
audio_formats = []
|
81
|
+
|
82
|
+
for fmt in formats:
|
83
|
+
if fmt.get('vcodec') != 'none' and fmt.get('acodec') != 'none':
|
84
|
+
# Video + Audio
|
85
|
+
video_formats.append({
|
86
|
+
'format_id': fmt.get('format_id'),
|
87
|
+
'ext': fmt.get('ext'),
|
88
|
+
'resolution': f"{fmt.get('width', 'N/A')}x{fmt.get('height', 'N/A')}",
|
89
|
+
'fps': fmt.get('fps'),
|
90
|
+
'filesize': fmt.get('filesize'),
|
91
|
+
})
|
92
|
+
elif fmt.get('acodec') != 'none':
|
93
|
+
# Audio only
|
94
|
+
audio_formats.append({
|
95
|
+
'format_id': fmt.get('format_id'),
|
96
|
+
'ext': fmt.get('ext'),
|
97
|
+
'abr': fmt.get('abr'),
|
98
|
+
'filesize': fmt.get('filesize'),
|
99
|
+
})
|
100
|
+
|
101
|
+
formatted_info['available_formats'] = {
|
102
|
+
'video_count': len(video_formats),
|
103
|
+
'audio_count': len(audio_formats),
|
104
|
+
'video_formats': video_formats[:5], # Show only the first 5
|
105
|
+
'audio_formats': audio_formats[:5], # Show only the first 5
|
106
|
+
}
|
107
|
+
|
108
|
+
return formatted_info
|
109
|
+
|
110
|
+
def print_info(self, info: Dict[str, Any]) -> None:
|
111
|
+
"""
|
112
|
+
Format and display information
|
113
|
+
|
114
|
+
Args:
|
115
|
+
info (Dict[str, Any]): Information to display
|
116
|
+
"""
|
117
|
+
if not info:
|
118
|
+
print("Could not retrieve information.")
|
119
|
+
return
|
120
|
+
|
121
|
+
print("=" * 60)
|
122
|
+
print(f"Title: {info['title']}")
|
123
|
+
print(f"Uploader: {info['uploader']}")
|
124
|
+
print(f"Upload Date: {info['upload_date']}")
|
125
|
+
print(
|
126
|
+
f"Duration: {info['duration']} seconds ({info['duration']//60} min {info['duration'] % 60} sec)")
|
127
|
+
print(f"View Count: {info['view_count']:,}")
|
128
|
+
print(f"Like Count: {info['like_count']:,}")
|
129
|
+
print(f"URL: {info['webpage_url']}")
|
130
|
+
print("-" * 60)
|
131
|
+
print(f"Description: {info['description']}")
|
132
|
+
print("-" * 60)
|
133
|
+
|
134
|
+
if info['tags']:
|
135
|
+
print(f"Tags: {', '.join(info['tags'][:10])}") # First 10 tags
|
136
|
+
|
137
|
+
if info['categories']:
|
138
|
+
print(f"Categories: {', '.join(info['categories'])}")
|
139
|
+
|
140
|
+
# Format information
|
141
|
+
if 'available_formats' in info:
|
142
|
+
formats = info['available_formats']
|
143
|
+
print(" Available formats:")
|
144
|
+
print(f" Video format count: {formats['video_count']}")
|
145
|
+
print(f" Audio format count: {formats['audio_count']}")
|
146
|
+
|
147
|
+
if formats['video_formats']:
|
148
|
+
print(" Main video formats:")
|
149
|
+
for fmt in formats['video_formats']:
|
150
|
+
size_info = f" ({fmt['filesize']//1024//1024}MB)" if fmt['filesize'] else ""
|
151
|
+
print(
|
152
|
+
f" - {fmt['format_id']}: {fmt['ext']}, {fmt['resolution']}, {fmt['fps']}fps{size_info}")
|
153
|
+
|
154
|
+
print("=" * 60)
|
{mcp_server_youtube_info-0.1.7.dist-info → mcp_server_youtube_info-0.1.8.dist-info}/METADATA
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: mcp-server-youtube-info
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.8
|
4
4
|
Summary: A YouTube information retrieval server implementation for Model Context Protocol (MCP)
|
5
5
|
Author-email: yareyaredesuyo <yareyaredesuyo@gmail.com>
|
6
6
|
License-Expression: MIT
|
@@ -0,0 +1,8 @@
|
|
1
|
+
mcp_server_youtube_info/__init__.py,sha256=sjMCe_DmMsjgbWKl1zRLTzzLDO8Ng9zgcTdQCHT_PT0,1598
|
2
|
+
mcp_server_youtube_info/__main__.py,sha256=uqSLNeSh8GpjfJjyKqL_tvsDIryo3G_SYCmTqtDnPhs,58
|
3
|
+
mcp_server_youtube_info/server.py,sha256=ZxmofLBeslu9vnOLiYTYvN-iUZW-197b4p1zpfB2JrM,2734
|
4
|
+
mcp_server_youtube_info/util.py,sha256=xy6ky8Ou-10NIEXuhcTnxo5HXaDthDkH_QJc6zkH1uw,5739
|
5
|
+
mcp_server_youtube_info-0.1.8.dist-info/METADATA,sha256=4JH5nG_2JRrAH5r9f47l_uLzHLkPKrp25Lgf0DeiBFA,3508
|
6
|
+
mcp_server_youtube_info-0.1.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
7
|
+
mcp_server_youtube_info-0.1.8.dist-info/entry_points.txt,sha256=CVOJ38APpxqkwNxKV4Bb7kHLC_Zbg8ZjnAevxUONEkk,73
|
8
|
+
mcp_server_youtube_info-0.1.8.dist-info/RECORD,,
|
@@ -1,7 +0,0 @@
|
|
1
|
-
mcp_server_youtube_info/__init__.py,sha256=sjMCe_DmMsjgbWKl1zRLTzzLDO8Ng9zgcTdQCHT_PT0,1598
|
2
|
-
mcp_server_youtube_info/__main__.py,sha256=uqSLNeSh8GpjfJjyKqL_tvsDIryo3G_SYCmTqtDnPhs,58
|
3
|
-
mcp_server_youtube_info/server.py,sha256=plu-Y_A-5nGIeJ2J2fU4LBiw45cixBv5seB4Lo3EyaE,2948
|
4
|
-
mcp_server_youtube_info-0.1.7.dist-info/METADATA,sha256=k9BSDB4U_J53Z1-9fhHBOkY1ILqiMDmgzkaT3pNLD0M,3508
|
5
|
-
mcp_server_youtube_info-0.1.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
6
|
-
mcp_server_youtube_info-0.1.7.dist-info/entry_points.txt,sha256=CVOJ38APpxqkwNxKV4Bb7kHLC_Zbg8ZjnAevxUONEkk,73
|
7
|
-
mcp_server_youtube_info-0.1.7.dist-info/RECORD,,
|
File without changes
|
{mcp_server_youtube_info-0.1.7.dist-info → mcp_server_youtube_info-0.1.8.dist-info}/entry_points.txt
RENAMED
File without changes
|