Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -7,6 +7,11 @@ from bs4 import BeautifulSoup
|
|
7 |
import os
|
8 |
from datetime import datetime, timedelta
|
9 |
import time
|
|
|
|
|
|
|
|
|
|
|
10 |
|
11 |
# Configure logging
|
12 |
logging.basicConfig(
|
@@ -104,28 +109,83 @@ def extract_track_id(track_url: str) -> str:
|
|
104 |
logger.error(f"Failed to extract track ID from URL {track_url}: {str(e)}")
|
105 |
raise HTTPException(status_code=400, detail="Invalid Spotify URL format")
|
106 |
|
107 |
-
|
108 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
109 |
apis = [
|
110 |
-
|
111 |
-
|
112 |
]
|
113 |
|
114 |
-
|
|
|
|
|
|
|
115 |
try:
|
116 |
logger.info(f"Attempting to get download URL from: {api_url}")
|
117 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
118 |
if response.status_code == 200:
|
119 |
download_url = response.json().get("url")
|
120 |
if download_url:
|
121 |
logger.info(f"Successfully obtained download URL from {api_url}")
|
122 |
-
return download_url
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
123 |
except requests.exceptions.RequestException as e:
|
124 |
logger.warning(f"Failed to get download URL from {api_url}: {str(e)}")
|
|
|
|
|
|
|
|
|
|
|
125 |
continue
|
126 |
|
127 |
-
|
128 |
-
|
|
|
|
|
|
|
|
|
|
|
129 |
|
130 |
@app.get("/get-track")
|
131 |
async def get_track(
|
@@ -133,10 +193,7 @@ async def get_track(
|
|
133 |
track_id: Optional[str] = None,
|
134 |
track_url: Optional[str] = None
|
135 |
):
|
136 |
-
"""
|
137 |
-
Get track information and download URL.
|
138 |
-
Requires either track_id or track_url parameter.
|
139 |
-
"""
|
140 |
request_id = datetime.now().strftime('%Y%m%d%H%M%S%f')
|
141 |
logger.info(f"Request {request_id} started - Track ID: {track_id}, URL: {track_url}")
|
142 |
|
@@ -183,21 +240,26 @@ async def get_track(
|
|
183 |
|
184 |
track_data = response.json()
|
185 |
|
186 |
-
# Get
|
187 |
-
|
|
|
|
|
|
|
188 |
|
189 |
# Prepare response
|
190 |
result = {
|
191 |
"track_id": track_id,
|
192 |
-
"name":
|
193 |
-
"album":
|
194 |
-
"artist":
|
195 |
-
"release_date":
|
196 |
-
"duration_ms":
|
197 |
-
"url": download_url
|
|
|
198 |
}
|
199 |
|
200 |
-
|
|
|
201 |
return result
|
202 |
|
203 |
except HTTPException:
|
@@ -206,7 +268,8 @@ async def get_track(
|
|
206 |
logger.error(f"Request {request_id} failed with error: {str(e)}")
|
207 |
raise HTTPException(status_code=500, detail="Internal server error")
|
208 |
|
209 |
-
|
|
|
210 |
async def health_check():
|
211 |
"""Health check endpoint."""
|
212 |
try:
|
|
|
7 |
import os
|
8 |
from datetime import datetime, timedelta
|
9 |
import time
|
10 |
+
import requests
|
11 |
+
from requests.adapters import HTTPAdapter
|
12 |
+
from urllib3.util.retry import Retry
|
13 |
+
import asyncio
|
14 |
+
from typing import Optional, Dict, Tuple
|
15 |
|
16 |
# Configure logging
|
17 |
logging.basicConfig(
|
|
|
109 |
logger.error(f"Failed to extract track ID from URL {track_url}: {str(e)}")
|
110 |
raise HTTPException(status_code=400, detail="Invalid Spotify URL format")
|
111 |
|
112 |
+
# Constants
|
113 |
+
DOWNLOAD_TIMEOUT = 30 # Increased timeout for download URLs
|
114 |
+
MAX_RETRIES = 3
|
115 |
+
BACKOFF_FACTOR = 0.5 # Will sleep for [0.5, 1.0, 2.0] seconds between retries
|
116 |
+
|
117 |
+
# Create a session with retry strategy
|
118 |
+
def create_request_session() -> requests.Session:
|
119 |
+
session = requests.Session()
|
120 |
+
retry_strategy = Retry(
|
121 |
+
total=MAX_RETRIES,
|
122 |
+
backoff_factor=BACKOFF_FACTOR,
|
123 |
+
status_forcelist=[408, 429, 500, 502, 503, 504],
|
124 |
+
)
|
125 |
+
adapter = HTTPAdapter(max_retries=retry_strategy)
|
126 |
+
session.mount("http://", adapter)
|
127 |
+
session.mount("https://", adapter)
|
128 |
+
return session
|
129 |
+
|
130 |
+
# Create a session to be reused
|
131 |
+
session = create_request_session()
|
132 |
+
|
133 |
+
async def get_track_download_url(track_id: str) -> Tuple[str, str]:
|
134 |
+
"""
|
135 |
+
Get download URL with fallback and retry logic.
|
136 |
+
Returns tuple of (url, api_version) where api_version is 'v1' or 'v2'
|
137 |
+
"""
|
138 |
apis = [
|
139 |
+
("https://downspotifyapis.vercel.app/api/v2/track/", "v2"),
|
140 |
+
("https://downspotifyapis.vercel.app/api/v1/track/", "v1")
|
141 |
]
|
142 |
|
143 |
+
errors = []
|
144 |
+
|
145 |
+
for api_base, version in apis:
|
146 |
+
api_url = f"{api_base}{track_id}"
|
147 |
try:
|
148 |
logger.info(f"Attempting to get download URL from: {api_url}")
|
149 |
+
|
150 |
+
# Make the request with increased timeout and session
|
151 |
+
response = session.get(
|
152 |
+
api_url,
|
153 |
+
timeout=DOWNLOAD_TIMEOUT,
|
154 |
+
headers={'User-Agent': 'Mozilla/5.0'} # Adding user agent to prevent some blocks
|
155 |
+
)
|
156 |
+
|
157 |
if response.status_code == 200:
|
158 |
download_url = response.json().get("url")
|
159 |
if download_url:
|
160 |
logger.info(f"Successfully obtained download URL from {api_url}")
|
161 |
+
return download_url, version
|
162 |
+
else:
|
163 |
+
logger.warning(f"No URL in response from {api_url}")
|
164 |
+
errors.append(f"{version}: Empty URL in response")
|
165 |
+
else:
|
166 |
+
logger.warning(f"Failed response from {api_url}: {response.status_code}")
|
167 |
+
errors.append(f"{version}: Status {response.status_code}")
|
168 |
+
|
169 |
+
except requests.exceptions.Timeout as e:
|
170 |
+
logger.warning(f"Timeout for {api_url}: {str(e)}")
|
171 |
+
errors.append(f"{version}: Timeout after {DOWNLOAD_TIMEOUT}s")
|
172 |
+
continue
|
173 |
except requests.exceptions.RequestException as e:
|
174 |
logger.warning(f"Failed to get download URL from {api_url}: {str(e)}")
|
175 |
+
errors.append(f"{version}: {str(e)}")
|
176 |
+
continue
|
177 |
+
except Exception as e:
|
178 |
+
logger.error(f"Unexpected error for {api_url}: {str(e)}")
|
179 |
+
errors.append(f"{version}: Unexpected error - {str(e)}")
|
180 |
continue
|
181 |
|
182 |
+
# If we get here, both APIs failed
|
183 |
+
error_msg = " | ".join(errors)
|
184 |
+
logger.error(f"All download URL attempts failed for track {track_id}: {error_msg}")
|
185 |
+
raise HTTPException(
|
186 |
+
status_code=404,
|
187 |
+
detail=f"Download URL not found. Errors: {error_msg}"
|
188 |
+
)
|
189 |
|
190 |
@app.get("/get-track")
|
191 |
async def get_track(
|
|
|
193 |
track_id: Optional[str] = None,
|
194 |
track_url: Optional[str] = None
|
195 |
):
|
196 |
+
"""Get track information and download URL."""
|
|
|
|
|
|
|
197 |
request_id = datetime.now().strftime('%Y%m%d%H%M%S%f')
|
198 |
logger.info(f"Request {request_id} started - Track ID: {track_id}, URL: {track_url}")
|
199 |
|
|
|
240 |
|
241 |
track_data = response.json()
|
242 |
|
243 |
+
# Get track metadata from Spotify
|
244 |
+
spotify_response = track_data
|
245 |
+
|
246 |
+
# Get download URL with version info
|
247 |
+
download_url, api_version = await get_track_download_url(track_id)
|
248 |
|
249 |
# Prepare response
|
250 |
result = {
|
251 |
"track_id": track_id,
|
252 |
+
"name": spotify_response["name"],
|
253 |
+
"album": spotify_response["album"]["name"],
|
254 |
+
"artist": spotify_response["artists"][0]["name"],
|
255 |
+
"release_date": spotify_response["album"]["release_date"],
|
256 |
+
"duration_ms": spotify_response["duration_ms"],
|
257 |
+
"url": download_url,
|
258 |
+
"download_api_version": api_version
|
259 |
}
|
260 |
|
261 |
+
duration = time.time() - start_time
|
262 |
+
logger.info(f"Request {request_id} completed successfully in {duration:.2f}s using API {api_version}")
|
263 |
return result
|
264 |
|
265 |
except HTTPException:
|
|
|
268 |
logger.error(f"Request {request_id} failed with error: {str(e)}")
|
269 |
raise HTTPException(status_code=500, detail="Internal server error")
|
270 |
|
271 |
+
# ... (rest of the code remains the same)
|
272 |
+
@app.get("/")
|
273 |
async def health_check():
|
274 |
"""Health check endpoint."""
|
275 |
try:
|