Nikita Makarov commited on
Commit
bb97e91
·
1 Parent(s): 3229c47

Add Modal proxy for YouTube search (bypasses HF Spaces DNS restrictions)

Browse files
Files changed (2) hide show
  1. MODAL_SETUP.md +32 -0
  2. modal_proxy.py +107 -0
MODAL_SETUP.md ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Modal YouTube Proxy Setup Instructions
2
+
3
+ ## 1. Install Modal CLI
4
+ pip install modal
5
+
6
+ ## 2. Login to Modal
7
+ modal login
8
+
9
+ ## 3. Create YouTube API Key (Optional)
10
+ # Go to https://console.developers.google.com/
11
+ # Create project, enable YouTube Data API v3
12
+ # Create API key and add to Modal secrets:
13
+ modal secret create youtube-api-key YOUTUBE_API_KEY=your_key_here
14
+
15
+ ## 4. Deploy the proxy
16
+ modal deploy modal_proxy.py
17
+
18
+ ## 5. Get the URL
19
+ # After deployment, Modal will show the URL like:
20
+ # https://your-modal-app.modal.run
21
+
22
+ ## 6. Update music_server.py
23
+ # Replace 'your-modal-app.modal.run' with your actual Modal URL
24
+
25
+ ## 7. Push to HF Spaces
26
+ git add .
27
+ git commit -m 'Add Modal proxy support'
28
+ git push hf main --force
29
+
30
+ ## Testing
31
+ curl 'https://your-modal-app.modal.run/youtube-search?query=drake&limit=3'
32
+
modal_proxy.py ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ YouTube Search Proxy for Modal
4
+ Bypasses DNS restrictions in HuggingFace Spaces
5
+ """
6
+ import modal
7
+ import requests
8
+ import json
9
+ from typing import Dict, List, Any
10
+
11
+ # Create Modal app
12
+ app = modal.App("youtube-proxy")
13
+
14
+ # Define image with dependencies
15
+ image = modal.Image.debian_slim().pip_install(
16
+ "requests",
17
+ "fastapi",
18
+ "uvicorn"
19
+ )
20
+
21
+ @app.function(image=image, secrets=[modal.Secret.from_name("youtube-api-key")])
22
+ @modal.web_endpoint(method="GET", label="youtube-search")
23
+ def search_youtube(query: str, limit: int = 5) -> Dict[str, Any]:
24
+ """Search YouTube via official API"""
25
+ try:
26
+ # YouTube Data API v3 search endpoint
27
+ url = "https://www.googleapis.com/youtube/v3/search"
28
+ params = {
29
+ 'part': 'snippet',
30
+ 'q': f"{query} music",
31
+ 'type': 'video',
32
+ 'maxResults': limit,
33
+ 'order': 'relevance',
34
+ 'safeSearch': 'moderate',
35
+ 'key': "" # Will use Modal secret
36
+ }
37
+
38
+ response = requests.get(url, params=params, timeout=30)
39
+ response.raise_for_status()
40
+ data = response.json()
41
+
42
+ tracks = []
43
+ if 'items' in data:
44
+ for item in data['items'][:limit]:
45
+ if item.get('id', {}).get('videoId'):
46
+ video_id = item['id']['videoId']
47
+ snippet = item.get('snippet', {})
48
+
49
+ track = {
50
+ "title": snippet.get('title', 'Unknown'),
51
+ "artist": snippet.get('channelTitle', 'Unknown Artist'),
52
+ "url": f"https://www.youtube.com/watch?v={video_id}",
53
+ "youtube_id": video_id,
54
+ "duration": 0,
55
+ "genre": query.split()[0] if query else "unknown",
56
+ "source": "youtube_api",
57
+ "thumbnail": snippet.get('thumbnails', {}).get('default', {}).get('url', '')
58
+ }
59
+ tracks.append(track)
60
+
61
+ return {"success": True, "tracks": tracks}
62
+
63
+ except Exception as e:
64
+ return {"success": False, "error": str(e), "tracks": []}
65
+
66
+ @app.function(image=image)
67
+ @modal.web_endpoint(method="GET", label="soundcloud-search")
68
+ def search_soundcloud(query: str, limit: int = 5) -> Dict[str, Any]:
69
+ """Search SoundCloud via API"""
70
+ try:
71
+ # SoundCloud API search
72
+ search_query = f"{query} music"
73
+ url = f"https://api-v2.soundcloud.com/search/tracks"
74
+ params = {
75
+ 'q': search_query,
76
+ 'limit': limit,
77
+ }
78
+
79
+ response = requests.get(url, params=params, timeout=30)
80
+ if response.status_code == 200:
81
+ data = response.json()
82
+ tracks = []
83
+
84
+ if 'collection' in data:
85
+ for item in data['collection'][:limit]:
86
+ if item.get('streamable'):
87
+ track = {
88
+ "title": item.get('title', 'Unknown'),
89
+ "artist": item.get('user', {}).get('username', 'Unknown Artist'),
90
+ "url": item.get('permalink_url', ''),
91
+ "duration": item.get('duration', 0) // 1000,
92
+ "genre": query.split()[0] if query else "unknown",
93
+ "source": "soundcloud_api"
94
+ }
95
+ tracks.append(track)
96
+
97
+ return {"success": True, "tracks": tracks}
98
+ else:
99
+ return {"success": False, "error": f"HTTP {response.status_code}", "tracks": []}
100
+
101
+ except Exception as e:
102
+ return {"success": False, "error": str(e), "tracks": []}
103
+
104
+ if __name__ == "__main__":
105
+ # For local testing
106
+ print("Modal YouTube Proxy")
107
+ print("Deploy with: modal deploy modal_proxy.py")