spotify-clone/backend/api/endpoints/stream.py

92 lines
3.5 KiB
Python

from fastapi import APIRouter, HTTPException, Depends
from fastapi.responses import StreamingResponse
from backend.services.youtube import YouTubeService
import requests
router = APIRouter()
def get_youtube_service():
return YouTubeService()
@router.get("/stream")
async def stream_audio(id: str, yt: YouTubeService = Depends(get_youtube_service)):
try:
data = yt.get_stream_url(id)
if isinstance(data, dict):
stream_url = data.get("url")
headers = data.get("headers", {})
else:
# Fallback for old cached string values
stream_url = data
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
}
# Helper function to get stream
def get_stream(url, headers):
return requests.get(url, headers=headers, stream=True, timeout=10)
try:
r = get_stream(stream_url, headers)
r.raise_for_status()
except requests.exceptions.HTTPError as e:
if e.response.status_code == 403:
print("Got 403 Forbidden. Invalidating cache and retrying...")
yt.invalidate_stream_cache(id)
# Fetch fresh
data = yt.get_stream_url(id)
if isinstance(data, dict):
stream_url = data.get("url")
headers = data.get("headers", {})
else:
stream_url = data
# Retry request
r = get_stream(stream_url, headers)
r.raise_for_status()
else:
raise e
def iterfile():
# Already opened request 'r'
try:
for chunk in r.iter_content(chunk_size=64*1024):
yield chunk
except Exception as e:
print(f"Chunk Error: {e}")
finally:
r.close()
return StreamingResponse(iterfile(), media_type="audio/mpeg")
except Exception as e:
print(f"Stream Error: {e}")
if isinstance(e, requests.exceptions.HTTPError):
print(f"Upstream Status: {e.response.status_code}")
raise HTTPException(status_code=500, detail=str(e))
@router.get("/download")
async def download_audio(id: str, title: str = "audio", yt: YouTubeService = Depends(get_youtube_service)):
try:
data = yt.get_stream_url(id)
if isinstance(data, dict):
stream_url = data.get("url")
headers = data.get("headers", {})
else:
stream_url = data
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
}
def iterfile():
with requests.get(stream_url, headers=headers, stream=True, timeout=10) as r:
r.raise_for_status()
for chunk in r.iter_content(chunk_size=1024*1024):
yield chunk
safe_filename = "".join([c for c in title if c.isalnum() or c in (' ', '-', '_')]).strip()
final_headers = {
"Content-Disposition": f'attachment; filename="{safe_filename}.mp3"'
}
return StreamingResponse(iterfile(), media_type="audio/mpeg", headers=final_headers)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))