Chrunos commited on
Commit
b6fc0a2
·
verified ·
1 Parent(s): 4d0d26b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +160 -44
app.py CHANGED
@@ -1,61 +1,177 @@
1
  import base64
 
 
 
2
  from fastapi import FastAPI, HTTPException, Request
3
  import requests
4
  from bs4 import BeautifulSoup
5
  import os
 
 
6
 
7
- app = FastAPI()
 
 
 
 
 
 
 
 
 
8
 
 
 
 
 
9
  SPOTIFY_API_URL = "https://api.spotify.com/v1"
10
  SPOTIFY_CLIENT_ID = os.getenv("SPOTIFY_CLIENT_ID")
11
  SPOTIFY_CLIENT_SECRET = os.getenv("SPOTIFY_CLIENT_SECRET")
 
 
 
 
 
12
 
 
 
13
  def get_spotify_token():
14
- auth_response = requests.post(
15
- 'https://accounts.spotify.com/api/token',
16
- data = {
17
- 'grant_type': 'client_credentials'
18
- },
19
- headers = {
20
- 'Authorization': f'Basic {base64.b64encode(f"{SPOTIFY_CLIENT_ID}:{SPOTIFY_CLIENT_SECRET}".encode()).decode()}'
21
- }
22
- )
23
- auth_response_data = auth_response.json()
24
- return auth_response_data['access_token']
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
 
26
- @app.get("/get-track")
27
- def get_track(request: Request, track_id: str = None, track_url: str = None):
28
- if not track_id and not track_url:
29
- raise HTTPException(status_code=400, detail="Track ID or Track URL must be provided")
30
-
31
- access_token = get_spotify_token()
32
- headers = {
33
- "Authorization": f"Bearer {access_token}"
34
- }
 
 
 
 
 
35
 
36
- if track_url:
37
- track_id = track_url.split("/")[-1]
38
-
39
- # First request to get download URL
40
- response = requests.get(f"{SPOTIFY_API_URL}/tracks/{track_id}", headers=headers)
41
- track_data = response.json()
 
 
 
 
 
 
42
 
43
- respone = requests.get(f"https://downspotifyapis.vercel.app/api/v2/track/{track_id}")
44
- download_res = respone.json()
 
 
 
 
 
 
 
 
 
 
 
 
 
45
 
46
- # If URL is not valid, send another request to second URL
47
- url = download_res.get("url")
48
- if not url:
49
- respone = requests.get(f"https://downspotifyapis.vercel.app/api/v1/track/{track_id}")
50
- download_res = respone.json()
51
- url = download_res.get("url")
52
 
53
- return {
54
- "track_id": track_id,
55
- "name": track_data["name"],
56
- "album": track_data["album"]["name"],
57
- "artist": track_data["artists"][0]["name"],
58
- "release_date": track_data["album"]["release_date"],
59
- "duration_ms": track_data["duration_ms"],
60
- "url": url
61
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import base64
2
+ import logging
3
+ from typing import Optional
4
+ from functools import lru_cache
5
  from fastapi import FastAPI, HTTPException, Request
6
  import requests
7
  from bs4 import BeautifulSoup
8
  import os
9
+ from datetime import datetime
10
+ import time
11
 
12
+ # Configure logging
13
+ logging.basicConfig(
14
+ level=logging.INFO,
15
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
16
+ handlers=[
17
+ logging.StreamHandler(),
18
+ logging.FileHandler('spotify_api.log')
19
+ ]
20
+ )
21
+ logger = logging.getLogger(__name__)
22
 
23
+ app = FastAPI(title="Spotify Track API",
24
+ description="API for retrieving Spotify track information and download URLs")
25
+
26
+ # Constants
27
  SPOTIFY_API_URL = "https://api.spotify.com/v1"
28
  SPOTIFY_CLIENT_ID = os.getenv("SPOTIFY_CLIENT_ID")
29
  SPOTIFY_CLIENT_SECRET = os.getenv("SPOTIFY_CLIENT_SECRET")
30
+ TOKEN_EXPIRY = 3600 # Spotify token expires in 1 hour
31
+
32
+ # Custom exception for Spotify API errors
33
+ class SpotifyAPIError(Exception):
34
+ pass
35
 
36
+ # Cache token for 1 hour
37
+ @lru_cache(maxsize=1)
38
  def get_spotify_token():
39
+ try:
40
+ logger.info("Requesting new Spotify access token")
41
+ start_time = time.time()
42
+
43
+ if not SPOTIFY_CLIENT_ID or not SPOTIFY_CLIENT_SECRET:
44
+ raise SpotifyAPIError("Spotify credentials not configured")
45
+
46
+ auth_string = f"{SPOTIFY_CLIENT_ID}:{SPOTIFY_CLIENT_SECRET}"
47
+ auth_bytes = base64.b64encode(auth_string.encode()).decode()
48
+
49
+ auth_response = requests.post(
50
+ 'https://accounts.spotify.com/api/token',
51
+ data={'grant_type': 'client_credentials'},
52
+ headers={'Authorization': f'Basic {auth_bytes}'},
53
+ timeout=10
54
+ )
55
+
56
+ if auth_response.status_code != 200:
57
+ raise SpotifyAPIError(f"Failed to get token: {auth_response.text}")
58
+
59
+ token = auth_response.json()['access_token']
60
+ logger.info(f"Token obtained successfully in {time.time() - start_time:.2f}s")
61
+ return token
62
+
63
+ except requests.exceptions.RequestException as e:
64
+ logger.error(f"Network error during token request: {str(e)}")
65
+ raise HTTPException(status_code=503, detail="Spotify authentication service unavailable")
66
+ except Exception as e:
67
+ logger.error(f"Unexpected error during token request: {str(e)}")
68
+ raise HTTPException(status_code=500, detail="Internal server error")
69
 
70
+ def extract_track_id(track_url: str) -> str:
71
+ """Extract track ID from Spotify URL."""
72
+ try:
73
+ return track_url.split("/")[-1].split("?")[0]
74
+ except Exception as e:
75
+ logger.error(f"Failed to extract track ID from URL {track_url}: {str(e)}")
76
+ raise HTTPException(status_code=400, detail="Invalid Spotify URL format")
77
+
78
+ async def get_track_download_url(track_id: str) -> str:
79
+ """Get download URL with fallback."""
80
+ apis = [
81
+ f"https://downspotifyapis.vercel.app/api/v2/track/{track_id}",
82
+ f"https://downspotifyapis.vercel.app/api/v1/track/{track_id}"
83
+ ]
84
 
85
+ for api_url in apis:
86
+ try:
87
+ logger.info(f"Attempting to get download URL from: {api_url}")
88
+ response = requests.get(api_url, timeout=10)
89
+ if response.status_code == 200:
90
+ download_url = response.json().get("url")
91
+ if download_url:
92
+ logger.info(f"Successfully obtained download URL from {api_url}")
93
+ return download_url
94
+ except requests.exceptions.RequestException as e:
95
+ logger.warning(f"Failed to get download URL from {api_url}: {str(e)}")
96
+ continue
97
 
98
+ logger.error(f"No download URL found for track {track_id}")
99
+ raise HTTPException(status_code=404, detail="Download URL not found")
100
+
101
+ @app.get("/get-track")
102
+ async def get_track(
103
+ request: Request,
104
+ track_id: Optional[str] = None,
105
+ track_url: Optional[str] = None
106
+ ):
107
+ """
108
+ Get track information and download URL.
109
+ Requires either track_id or track_url parameter.
110
+ """
111
+ request_id = datetime.now().strftime('%Y%m%d%H%M%S%f')
112
+ logger.info(f"Request {request_id} started - Track ID: {track_id}, URL: {track_url}")
113
 
114
+ start_time = time.time()
 
 
 
 
 
115
 
116
+ try:
117
+ # Input validation
118
+ if not track_id and not track_url:
119
+ raise HTTPException(status_code=400, detail="Track ID or Track URL must be provided")
120
+
121
+ # Extract track ID from URL if provided
122
+ if track_url:
123
+ track_id = extract_track_id(track_url)
124
+
125
+ # Get Spotify API token
126
+ access_token = get_spotify_token()
127
+
128
+ # Get track metadata from Spotify
129
+ logger.info(f"Request {request_id} - Fetching track metadata for {track_id}")
130
+ response = requests.get(
131
+ f"{SPOTIFY_API_URL}/tracks/{track_id}",
132
+ headers={"Authorization": f"Bearer {access_token}"},
133
+ timeout=10
134
+ )
135
+
136
+ if response.status_code != 200:
137
+ logger.error(f"Request {request_id} - Spotify API error: {response.text}")
138
+ raise HTTPException(
139
+ status_code=response.status_code,
140
+ detail="Failed to fetch track information from Spotify"
141
+ )
142
+
143
+ track_data = response.json()
144
+
145
+ # Get download URL
146
+ download_url = await get_track_download_url(track_id)
147
+
148
+ # Prepare response
149
+ result = {
150
+ "track_id": track_id,
151
+ "name": track_data["name"],
152
+ "album": track_data["album"]["name"],
153
+ "artist": track_data["artists"][0]["name"],
154
+ "release_date": track_data["album"]["release_date"],
155
+ "duration_ms": track_data["duration_ms"],
156
+ "url": download_url
157
+ }
158
+
159
+ logger.info(f"Request {request_id} completed successfully in {time.time() - start_time:.2f}s")
160
+ return result
161
+
162
+ except HTTPException:
163
+ raise
164
+ except Exception as e:
165
+ logger.error(f"Request {request_id} failed with error: {str(e)}")
166
+ raise HTTPException(status_code=500, detail="Internal server error")
167
+
168
+ @app.get("/health")
169
+ async def health_check():
170
+ """Health check endpoint."""
171
+ try:
172
+ # Test Spotify API token generation
173
+ token = get_spotify_token()
174
+ return {"status": "healthy", "spotify_auth": "ok"}
175
+ except Exception as e:
176
+ logger.error(f"Health check failed: {str(e)}")
177
+ return {"status": "unhealthy", "error": str(e)}