mcp-server-youtube-info 0.1.6__tar.gz → 0.1.8__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-server-youtube-info
3
- Version: 0.1.6
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
@@ -81,7 +81,7 @@ Add to your Claude settings:
81
81
  ```json
82
82
  {
83
83
  "mcpServers": {
84
- "testing": {
84
+ "youtube-info": {
85
85
  "command": "uvx",
86
86
  "args": ["mcp-server-youtube-info"]
87
87
  }
@@ -60,7 +60,7 @@ Add to your Claude settings:
60
60
  ```json
61
61
  {
62
62
  "mcpServers": {
63
- "testing": {
63
+ "youtube-info": {
64
64
  "command": "uvx",
65
65
  "args": ["mcp-server-youtube-info"]
66
66
  }
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mcp-server-youtube-info"
3
- version = "0.1.6"
3
+ version = "0.1.8"
4
4
  description = "A YouTube information retrieval server implementation for Model Context Protocol (MCP)"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -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
- return _extract_info(video_id)
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
- info = _extract_info(video_id)
59
- thumbnail_url = info.get('thumbnail')
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
- info = _extract_info(video_id)
78
- thumbnail_url = info.get('thumbnail')
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)