92 lines
3.5 KiB
Python
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))
|