spotifyapi / app.py
Chrunos's picture
Update app.py
b6fc0a2 verified
raw
history blame
6.46 kB
import base64
import logging
from typing import Optional
from functools import lru_cache
from fastapi import FastAPI, HTTPException, Request
import requests
from bs4 import BeautifulSoup
import os
from datetime import datetime
import time
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler(),
logging.FileHandler('spotify_api.log')
]
)
logger = logging.getLogger(__name__)
app = FastAPI(title="Spotify Track API",
description="API for retrieving Spotify track information and download URLs")
# Constants
SPOTIFY_API_URL = "https://api.spotify.com/v1"
SPOTIFY_CLIENT_ID = os.getenv("SPOTIFY_CLIENT_ID")
SPOTIFY_CLIENT_SECRET = os.getenv("SPOTIFY_CLIENT_SECRET")
TOKEN_EXPIRY = 3600 # Spotify token expires in 1 hour
# Custom exception for Spotify API errors
class SpotifyAPIError(Exception):
pass
# Cache token for 1 hour
@lru_cache(maxsize=1)
def get_spotify_token():
try:
logger.info("Requesting new Spotify access token")
start_time = time.time()
if not SPOTIFY_CLIENT_ID or not SPOTIFY_CLIENT_SECRET:
raise SpotifyAPIError("Spotify credentials not configured")
auth_string = f"{SPOTIFY_CLIENT_ID}:{SPOTIFY_CLIENT_SECRET}"
auth_bytes = base64.b64encode(auth_string.encode()).decode()
auth_response = requests.post(
'https://accounts.spotify.com/api/token',
data={'grant_type': 'client_credentials'},
headers={'Authorization': f'Basic {auth_bytes}'},
timeout=10
)
if auth_response.status_code != 200:
raise SpotifyAPIError(f"Failed to get token: {auth_response.text}")
token = auth_response.json()['access_token']
logger.info(f"Token obtained successfully in {time.time() - start_time:.2f}s")
return token
except requests.exceptions.RequestException as e:
logger.error(f"Network error during token request: {str(e)}")
raise HTTPException(status_code=503, detail="Spotify authentication service unavailable")
except Exception as e:
logger.error(f"Unexpected error during token request: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
def extract_track_id(track_url: str) -> str:
"""Extract track ID from Spotify URL."""
try:
return track_url.split("/")[-1].split("?")[0]
except Exception as e:
logger.error(f"Failed to extract track ID from URL {track_url}: {str(e)}")
raise HTTPException(status_code=400, detail="Invalid Spotify URL format")
async def get_track_download_url(track_id: str) -> str:
"""Get download URL with fallback."""
apis = [
f"https://downspotifyapis.vercel.app/api/v2/track/{track_id}",
f"https://downspotifyapis.vercel.app/api/v1/track/{track_id}"
]
for api_url in apis:
try:
logger.info(f"Attempting to get download URL from: {api_url}")
response = requests.get(api_url, timeout=10)
if response.status_code == 200:
download_url = response.json().get("url")
if download_url:
logger.info(f"Successfully obtained download URL from {api_url}")
return download_url
except requests.exceptions.RequestException as e:
logger.warning(f"Failed to get download URL from {api_url}: {str(e)}")
continue
logger.error(f"No download URL found for track {track_id}")
raise HTTPException(status_code=404, detail="Download URL not found")
@app.get("/get-track")
async def get_track(
request: Request,
track_id: Optional[str] = None,
track_url: Optional[str] = None
):
"""
Get track information and download URL.
Requires either track_id or track_url parameter.
"""
request_id = datetime.now().strftime('%Y%m%d%H%M%S%f')
logger.info(f"Request {request_id} started - Track ID: {track_id}, URL: {track_url}")
start_time = time.time()
try:
# Input validation
if not track_id and not track_url:
raise HTTPException(status_code=400, detail="Track ID or Track URL must be provided")
# Extract track ID from URL if provided
if track_url:
track_id = extract_track_id(track_url)
# Get Spotify API token
access_token = get_spotify_token()
# Get track metadata from Spotify
logger.info(f"Request {request_id} - Fetching track metadata for {track_id}")
response = requests.get(
f"{SPOTIFY_API_URL}/tracks/{track_id}",
headers={"Authorization": f"Bearer {access_token}"},
timeout=10
)
if response.status_code != 200:
logger.error(f"Request {request_id} - Spotify API error: {response.text}")
raise HTTPException(
status_code=response.status_code,
detail="Failed to fetch track information from Spotify"
)
track_data = response.json()
# Get download URL
download_url = await get_track_download_url(track_id)
# Prepare response
result = {
"track_id": track_id,
"name": track_data["name"],
"album": track_data["album"]["name"],
"artist": track_data["artists"][0]["name"],
"release_date": track_data["album"]["release_date"],
"duration_ms": track_data["duration_ms"],
"url": download_url
}
logger.info(f"Request {request_id} completed successfully in {time.time() - start_time:.2f}s")
return result
except HTTPException:
raise
except Exception as e:
logger.error(f"Request {request_id} failed with error: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
@app.get("/health")
async def health_check():
"""Health check endpoint."""
try:
# Test Spotify API token generation
token = get_spotify_token()
return {"status": "healthy", "spotify_auth": "ok"}
except Exception as e:
logger.error(f"Health check failed: {str(e)}")
return {"status": "unhealthy", "error": str(e)}