#!/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" ) @app.function(image=image, secrets=[modal.Secret.from_name("youtubeapikey")]) @modal.fastapi_endpoint(method="GET", label="youtube-search") 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": []} @app.function(image=image) @modal.fastapi_endpoint(method="GET", label="soundcloud-search") 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")