Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -14,6 +14,8 @@ from typing import Optional, Dict, Tuple
|
|
14 |
import urllib.parse
|
15 |
from fastapi.responses import JSONResponse
|
16 |
import re
|
|
|
|
|
17 |
|
18 |
# Configure logging
|
19 |
logging.basicConfig(
|
@@ -105,6 +107,23 @@ def get_spotify_token() -> str:
|
|
105 |
logger.error(f"Unexpected error during token request: {str(e)}")
|
106 |
raise HTTPException(status_code=500, detail="Internal server error")
|
107 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
108 |
def extract_album_id(album_url: str) -> str:
|
109 |
"""Extract album ID from Spotify URL."""
|
110 |
try:
|
@@ -112,6 +131,39 @@ def extract_album_id(album_url: str) -> str:
|
|
112 |
except Exception as e:
|
113 |
logger.error(f"Failed to extract album ID from URL {album_url}: {str(e)}")
|
114 |
raise HTTPException(status_code=400, detail="Invalid Spotify album URL format")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
115 |
|
116 |
|
117 |
@app.post("/album")
|
@@ -290,7 +342,7 @@ def get_url(track_data, cookie):
|
|
290 |
except:
|
291 |
return None
|
292 |
|
293 |
-
|
294 |
@app.get("/{track_id}")
|
295 |
async def download_track(track_id: str):
|
296 |
cookie = get_cookie()
|
@@ -306,11 +358,91 @@ async def download_track(track_id: str):
|
|
306 |
return {"error": "Failed to get download URL"}, 500
|
307 |
|
308 |
return {"url": download_link}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
309 |
|
|
|
|
|
|
|
310 |
|
|
|
311 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
312 |
|
313 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
314 |
|
315 |
@app.get("/")
|
316 |
async def health_check():
|
|
|
14 |
import urllib.parse
|
15 |
from fastapi.responses import JSONResponse
|
16 |
import re
|
17 |
+
from ytmusicapi import YTMusic
|
18 |
+
|
19 |
|
20 |
# Configure logging
|
21 |
logging.basicConfig(
|
|
|
107 |
logger.error(f"Unexpected error during token request: {str(e)}")
|
108 |
raise HTTPException(status_code=500, detail="Internal server error")
|
109 |
|
110 |
+
async def get_download_url(url):
|
111 |
+
loop = asyncio.get_running_loop()
|
112 |
+
url = "https://chrunos-ytdl2.hf.space/download"
|
113 |
+
data = {"url": url}
|
114 |
+
try:
|
115 |
+
response = await loop.run_in_executor(None, requests.post, url, {'json': data})
|
116 |
+
if response.status_code == 200:
|
117 |
+
result = response.json()
|
118 |
+
return result.get('download_url')
|
119 |
+
else:
|
120 |
+
logger.error(f"请求失败,状态码: {response.status_code}")
|
121 |
+
return None
|
122 |
+
except requests.RequestException as e:
|
123 |
+
logger.error(f"发生客户端错误: {e}")
|
124 |
+
return None
|
125 |
+
|
126 |
+
|
127 |
def extract_album_id(album_url: str) -> str:
|
128 |
"""Extract album ID from Spotify URL."""
|
129 |
try:
|
|
|
131 |
except Exception as e:
|
132 |
logger.error(f"Failed to extract album ID from URL {album_url}: {str(e)}")
|
133 |
raise HTTPException(status_code=400, detail="Invalid Spotify album URL format")
|
134 |
+
|
135 |
+
|
136 |
+
async def fetch_spotify_track_info(track_id: str) -> Dict:
|
137 |
+
"""
|
138 |
+
Asynchronously fetch Spotify track title, artist, and cover art URL.
|
139 |
+
"""
|
140 |
+
token = get_spotify_token()
|
141 |
+
headers = {
|
142 |
+
'Authorization': f'Bearer {token}'
|
143 |
+
}
|
144 |
+
url = f"{SPOTIFY_API_URL}/tracks/{track_id}"
|
145 |
+
try:
|
146 |
+
loop = asyncio.get_running_loop()
|
147 |
+
response = await loop.run_in_executor(None, requests.get, url, {'headers': headers})
|
148 |
+
if response.status_code == 200:
|
149 |
+
track_data = response.json()
|
150 |
+
title = track_data.get('name')
|
151 |
+
artists = [artist.get('name') for artist in track_data.get('artists', [])]
|
152 |
+
cover_art_url = track_data.get('album', {}).get('images', [{}])[0].get('url')
|
153 |
+
|
154 |
+
return {
|
155 |
+
'title': title,
|
156 |
+
'artists': artists,
|
157 |
+
'cover_art_url': cover_art_url
|
158 |
+
}
|
159 |
+
else:
|
160 |
+
raise SpotifyAPIError(f"Failed to fetch track information: {response.text}")
|
161 |
+
except requests.exceptions.RequestException as e:
|
162 |
+
logger.error(f"Network error during track information request: {str(e)}")
|
163 |
+
raise HTTPException(status_code=503, detail="Spotify API service unavailable")
|
164 |
+
except Exception as e:
|
165 |
+
logger.error(f"Unexpected error during track information request: {str(e)}")
|
166 |
+
raise HTTPException(status_code=500, detail="Internal server error")
|
167 |
|
168 |
|
169 |
@app.post("/album")
|
|
|
342 |
except:
|
343 |
return None
|
344 |
|
345 |
+
'''
|
346 |
@app.get("/{track_id}")
|
347 |
async def download_track(track_id: str):
|
348 |
cookie = get_cookie()
|
|
|
358 |
return {"error": "Failed to get download URL"}, 500
|
359 |
|
360 |
return {"url": download_link}
|
361 |
+
'''
|
362 |
+
|
363 |
+
@app.get("/{track_id}")
|
364 |
+
async def download_track(track_id: str):
|
365 |
+
url = f'https://open.spotify.com/track/{track_id}'
|
366 |
+
track_data = get_song_link_info(url)
|
367 |
+
if not track_data:
|
368 |
+
track_data = fetch_spotify_track_info(track_id)
|
369 |
+
title = track_data["title"]
|
370 |
+
artist = track_data["artist"]
|
371 |
+
query = f'{title}+{artist}'
|
372 |
+
logger.info(f"search query: {query}")
|
373 |
+
search_results = ytmusic.search(query, filter="songs")
|
374 |
+
first_song = next((song for song in search_results if 'videoId' in song and song['videoId']), {}) if search_results else {}
|
375 |
+
if 'videoId' in first_song:
|
376 |
+
videoId = first_song["videoId"]
|
377 |
+
ym_url = f'https://www.youtube.com/watch?v={videoId}'
|
378 |
+
d_data = get_download_url(ym_url)
|
379 |
+
track_data['download_url'] = d_data
|
380 |
+
return track_data
|
381 |
+
else:
|
382 |
+
yt_url = track_data['url']
|
383 |
+
d_data = get_download_url(yt_url)
|
384 |
+
track_data['download_url'] = d_data
|
385 |
+
return track_data
|
386 |
+
|
387 |
|
388 |
+
download_link = get_url(track_data, cookie)
|
389 |
+
if not download_link:
|
390 |
+
return {"error": "Failed to get download URL"}, 500
|
391 |
|
392 |
+
return {"url": download_link}
|
393 |
|
394 |
+
# Function to get track info from Song.link API
|
395 |
+
def get_song_link_info(url: str):
|
396 |
+
# Check if the URL is from Amazon Music
|
397 |
+
if "music.amazon.com" in url:
|
398 |
+
track_id = extract_amazon_track_id(url)
|
399 |
+
if track_id:
|
400 |
+
# Use the working format for Amazon Music tracks
|
401 |
+
api_url = f"https://api.song.link/v1-alpha.1/links?type=song&platform=amazonMusic&id={track_id}&userCountry=US"
|
402 |
+
else:
|
403 |
+
# If no track ID is found, use the original URL
|
404 |
+
api_url = f"https://api.song.link/v1-alpha.1/links?url={url}&userCountry=US"
|
405 |
+
else:
|
406 |
+
# For non-Amazon Music URLs, use the standard format
|
407 |
+
api_url = f"https://api.song.link/v1-alpha.1/links?url={url}&userCountry=US"
|
408 |
+
|
409 |
+
# Make the API call
|
410 |
+
response = requests.get(api_url)
|
411 |
+
if response.status_code == 200:
|
412 |
+
track_info = response
|
413 |
+
if not track_info:
|
414 |
+
raise HTTPException(status_code=404, detail="Could not fetch track info")
|
415 |
+
entityUniqueId = track_info["entityUniqueId"]
|
416 |
+
title = track_info["entitiesByUniqueId"][entityUniqueId]["title"]
|
417 |
+
artist = track_info["entitiesByUniqueId"][entityUniqueId]["artistName"]
|
418 |
+
filename = f"{title} - {artist}"
|
419 |
+
|
420 |
+
#extract YouTube URL
|
421 |
+
youtube_url = extract_url(track_info["linksByPlatform"], "youtube")
|
422 |
+
if youtube_url:
|
423 |
+
|
424 |
+
if title and artist:
|
425 |
+
filename = f"{title} - {artist}"
|
426 |
+
return {"url": youtube_url, "filename": filename}
|
427 |
+
else:
|
428 |
+
return {"url": youtube_url, "filename": "Unknown Track - Unknown Artist"}
|
429 |
+
else:
|
430 |
+
return None
|
431 |
+
else:
|
432 |
+
return None
|
433 |
|
434 |
|
435 |
+
'''
|
436 |
+
else:
|
437 |
+
query = f'{title}+{artist}'
|
438 |
+
logger.info(f"search query: {query}")
|
439 |
+
search_results = ytmusic.search(query, filter="songs")
|
440 |
+
first_song = next((song for song in search_results if 'videoId' in song and song['videoId']), {}) if search_results else {}
|
441 |
+
if 'videoId' in first_song:
|
442 |
+
videoId = first_song["videoId"]
|
443 |
+
ym_url = f'https://www.youtube.com/watch?v={videoId}'
|
444 |
+
return {"filename": filename, "url": ym_url, "track_id": videoId}
|
445 |
+
'''
|
446 |
|
447 |
@app.get("/")
|
448 |
async def health_check():
|