Spaces:
Sleeping
Sleeping
| #!/usr/bin/env python3 | |
| """ | |
| YouTube Search Proxy for Modal | |
| Bypasses DNS restrictions in HuggingFace Spaces | |
| """ | |
| import modal | |
| import requests | |
| import json | |
| import os | |
| from typing import Dict, List, Any | |
| # Create Modal app | |
| app = modal.App("youtube-proxy") | |
| # Define image with dependencies | |
| image = modal.Image.debian_slim().pip_install( | |
| "requests", | |
| "fastapi", | |
| "uvicorn" | |
| ) | |
| def search_youtube(query: str, limit: int = 5) -> Dict[str, Any]: | |
| """Search YouTube via official API""" | |
| try: | |
| # YouTube Data API v3 search endpoint | |
| url = "https://www.googleapis.com/youtube/v3/search" | |
| # Get API key from Modal secret | |
| # In Modal, secrets are injected as environment variables | |
| # The secret name "youtubeapikey" should make the key available as YOUTUBE_API_KEY | |
| api_key = os.environ.get("YOUTUBE_API_KEY", "") | |
| if not api_key: | |
| # If not found, try the secret name directly | |
| secret_data = os.environ.get("youtubeapikey", "") | |
| if secret_data: | |
| api_key = secret_data | |
| if not api_key: | |
| # Debug: show what environment variables are available | |
| available_vars = [k for k in os.environ.keys() if 'key' in k.lower() or 'api' in k.lower()] | |
| return {"success": False, "error": f"YouTube API key not found. Check Modal secret 'youtubeapikey' has YOUTUBE_API_KEY variable. Available key vars: {available_vars}", "tracks": []} | |
| params = { | |
| 'part': 'snippet', | |
| 'q': f"{query} music", | |
| 'type': 'video', | |
| 'maxResults': limit, | |
| 'order': 'relevance', | |
| 'safeSearch': 'moderate', | |
| 'key': api_key | |
| } | |
| response = requests.get(url, params=params, timeout=30) | |
| response.raise_for_status() | |
| data = response.json() | |
| tracks = [] | |
| if 'items' in data: | |
| for item in data['items'][:limit]: | |
| if item.get('id', {}).get('videoId'): | |
| video_id = item['id']['videoId'] | |
| snippet = item.get('snippet', {}) | |
| track = { | |
| "title": snippet.get('title', 'Unknown'), | |
| "artist": snippet.get('channelTitle', 'Unknown Artist'), | |
| "url": f"https://www.youtube.com/watch?v={video_id}", | |
| "youtube_id": video_id, | |
| "duration": 0, | |
| "genre": query.split()[0] if query else "unknown", | |
| "source": "youtube_api", | |
| "thumbnail": snippet.get('thumbnails', {}).get('default', {}).get('url', '') | |
| } | |
| tracks.append(track) | |
| return {"success": True, "tracks": tracks} | |
| except Exception as e: | |
| return {"success": False, "error": str(e), "tracks": []} | |
| def search_soundcloud(query: str, limit: int = 5) -> Dict[str, Any]: | |
| """Search SoundCloud via API""" | |
| try: | |
| # SoundCloud API search | |
| search_query = f"{query} music" | |
| url = f"https://api-v2.soundcloud.com/search/tracks" | |
| params = { | |
| 'q': search_query, | |
| 'limit': limit, | |
| } | |
| response = requests.get(url, params=params, timeout=30) | |
| if response.status_code == 200: | |
| data = response.json() | |
| tracks = [] | |
| if 'collection' in data: | |
| for item in data['collection'][:limit]: | |
| if item.get('streamable'): | |
| track = { | |
| "title": item.get('title', 'Unknown'), | |
| "artist": item.get('user', {}).get('username', 'Unknown Artist'), | |
| "url": item.get('permalink_url', ''), | |
| "duration": item.get('duration', 0) // 1000, | |
| "genre": query.split()[0] if query else "unknown", | |
| "source": "soundcloud_api" | |
| } | |
| tracks.append(track) | |
| return {"success": True, "tracks": tracks} | |
| else: | |
| return {"success": False, "error": f"HTTP {response.status_code}", "tracks": []} | |
| except Exception as e: | |
| return {"success": False, "error": str(e), "tracks": []} | |
| if __name__ == "__main__": | |
| # For local testing | |
| print("Modal YouTube Proxy") | |
| print("Deploy with: modal deploy modal_proxy.py") | |