Nikita Makarov commited on
Commit
e996d12
·
1 Parent(s): 2d5a12f

Add YouTube Data API v3 support to bypass DNS restrictions in HF Spaces

Browse files
Files changed (1) hide show
  1. src/mcp_servers/music_server.py +103 -5
src/mcp_servers/music_server.py CHANGED
@@ -133,6 +133,59 @@ class MusicMCPServer:
133
  self._embed_cache[video_id] = False
134
  return False
135
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
  def search_youtube_music(self, query: str, limit: int = 5, fast: bool = False, check_embed: bool = False) -> List[Dict[str, Any]]:
137
  """
138
  Search for free music on YouTube with retry logic for network issues
@@ -157,7 +210,16 @@ class MusicMCPServer:
157
  tracks = []
158
  max_retries = 3
159
  retry_delay = 2 # Start with 2 seconds
160
-
 
 
 
 
 
 
 
 
 
161
  for attempt in range(max_retries):
162
  try:
163
  # Try to resolve DNS first (helps diagnose network issues)
@@ -251,9 +313,9 @@ class MusicMCPServer:
251
  self._add_to_recently_played(video_id)
252
  print(f" ✓ Found: {track['title']} by {track['artist']}")
253
 
254
- # Success! Break out of retry loop
255
- break
256
-
257
  except (yt_dlp.utils.DownloadError, Exception) as e:
258
  error_str = str(e)
259
  # Check for DNS/network errors
@@ -275,7 +337,43 @@ class MusicMCPServer:
275
  break
276
 
277
  return tracks
278
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
279
  def search_soundcloud_music(self, query: str, limit: int = 5) -> List[Dict[str, Any]]:
280
  """
281
  Search for free music on SoundCloud
 
133
  self._embed_cache[video_id] = False
134
  return False
135
 
136
+ def search_youtube_via_api(self, query: str, limit: int = 5) -> List[Dict[str, Any]]:
137
+ """Search YouTube using official Data API v3 (works in restricted networks)"""
138
+ # This method uses HTTPS API instead of yt-dlp, which should work in HF Spaces
139
+ tracks = []
140
+
141
+ # Build search query
142
+ search_query = f"{query} music"
143
+ if "music" not in query.lower():
144
+ search_query = f"{query} music"
145
+
146
+ try:
147
+ # YouTube Data API v3 search endpoint
148
+ # Note: This requires a Google API key, but we'll try without one first
149
+ # The API has some quota without key, but it's limited
150
+ url = "https://www.googleapis.com/youtube/v3/search"
151
+ params = {
152
+ 'part': 'snippet',
153
+ 'q': search_query,
154
+ 'type': 'video',
155
+ 'maxResults': limit * 2, # Get more to filter
156
+ 'order': 'relevance',
157
+ 'safeSearch': 'moderate',
158
+ 'key': '' # Empty key for now - YouTube allows some requests without key
159
+ }
160
+
161
+ response = requests.get(url, params=params, timeout=30)
162
+ response.raise_for_status()
163
+ data = response.json()
164
+
165
+ if 'items' in data:
166
+ for item in data['items'][:limit]:
167
+ if item.get('id', {}).get('videoId'):
168
+ video_id = item['id']['videoId']
169
+ snippet = item.get('snippet', {})
170
+
171
+ track = {
172
+ "title": snippet.get('title', 'Unknown'),
173
+ "artist": snippet.get('channelTitle', 'Unknown Artist'),
174
+ "url": f"https://www.youtube.com/watch?v={video_id}",
175
+ "youtube_id": video_id,
176
+ "duration": 0, # We can't get duration without API key
177
+ "genre": query.split()[0] if query else "unknown",
178
+ "source": "youtube_api",
179
+ "thumbnail": snippet.get('thumbnails', {}).get('default', {}).get('url', '')
180
+ }
181
+ tracks.append(track)
182
+ print(f" ✓ Found via API: {track['title']} by {track['artist']}")
183
+
184
+ except Exception as e:
185
+ print(f"⚠️ YouTube API search failed: {e}")
186
+
187
+ return tracks
188
+
189
  def search_youtube_music(self, query: str, limit: int = 5, fast: bool = False, check_embed: bool = False) -> List[Dict[str, Any]]:
190
  """
191
  Search for free music on YouTube with retry logic for network issues
 
210
  tracks = []
211
  max_retries = 3
212
  retry_delay = 2 # Start with 2 seconds
213
+
214
+ # First, try YouTube Data API (works in restricted networks)
215
+ print("🔍 Trying YouTube Data API search...")
216
+ api_tracks = self.search_youtube_via_api(query, limit)
217
+ if api_tracks:
218
+ print(f"✅ Found {len(api_tracks)} tracks via YouTube API")
219
+ return api_tracks
220
+
221
+ print("⚠️ YouTube API failed, trying yt-dlp...")
222
+
223
  for attempt in range(max_retries):
224
  try:
225
  # Try to resolve DNS first (helps diagnose network issues)
 
313
  self._add_to_recently_played(video_id)
314
  print(f" ✓ Found: {track['title']} by {track['artist']}")
315
 
316
+ # Success! Break out of retry loop
317
+ break
318
+
319
  except (yt_dlp.utils.DownloadError, Exception) as e:
320
  error_str = str(e)
321
  # Check for DNS/network errors
 
337
  break
338
 
339
  return tracks
340
+
341
+ def search_soundcloud_via_api(self, query: str, limit: int = 5) -> List[Dict[str, Any]]:
342
+ """Search SoundCloud using unofficial API (works in restricted networks)"""
343
+ tracks = []
344
+
345
+ try:
346
+ # SoundCloud API doesn't require authentication for basic search
347
+ search_query = f"{query} music"
348
+ url = f"https://api-v2.soundcloud.com/search/tracks"
349
+ params = {
350
+ 'q': search_query,
351
+ 'limit': limit,
352
+ 'client_id': 'CLIENT_ID_PLACEHOLDER' # SoundCloud allows some requests without client_id
353
+ }
354
+
355
+ response = requests.get(url, params=params, timeout=30)
356
+ if response.status_code == 200:
357
+ data = response.json()
358
+ if 'collection' in data:
359
+ for item in data['collection'][:limit]:
360
+ if item.get('streamable'):
361
+ track = {
362
+ "title": item.get('title', 'Unknown'),
363
+ "artist": item.get('user', {}).get('username', 'Unknown Artist'),
364
+ "url": item.get('permalink_url', ''),
365
+ "duration": item.get('duration', 0) // 1000, # Convert from ms to seconds
366
+ "genre": query.split()[0] if query else "unknown",
367
+ "source": "soundcloud_api"
368
+ }
369
+ tracks.append(track)
370
+ print(f" ✓ Found on SoundCloud: {track['title']} by {track['artist']}")
371
+
372
+ except Exception as e:
373
+ print(f"⚠️ SoundCloud API search failed: {e}")
374
+
375
+ return tracks
376
+
377
  def search_soundcloud_music(self, query: str, limit: int = 5) -> List[Dict[str, Any]]:
378
  """
379
  Search for free music on SoundCloud