Fix: Add upstream headers to stream endpoint to prevent 403s
This commit is contained in:
parent
5f68476c76
commit
20f48529ba
3 changed files with 35 additions and 19 deletions
|
|
@ -11,10 +11,19 @@ def get_youtube_service():
|
||||||
@router.get("/stream")
|
@router.get("/stream")
|
||||||
async def stream_audio(id: str, yt: YouTubeService = Depends(get_youtube_service)):
|
async def stream_audio(id: str, yt: YouTubeService = Depends(get_youtube_service)):
|
||||||
try:
|
try:
|
||||||
stream_url = yt.get_stream_url(id)
|
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"
|
||||||
|
}
|
||||||
|
|
||||||
def iterfile():
|
def iterfile():
|
||||||
with requests.get(stream_url, stream=True) as r:
|
with requests.get(stream_url, headers=headers, stream=True, timeout=10) as r:
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
for chunk in r.iter_content(chunk_size=64*1024):
|
for chunk in r.iter_content(chunk_size=64*1024):
|
||||||
yield chunk
|
yield chunk
|
||||||
|
|
@ -22,23 +31,34 @@ async def stream_audio(id: str, yt: YouTubeService = Depends(get_youtube_service
|
||||||
return StreamingResponse(iterfile(), media_type="audio/mpeg")
|
return StreamingResponse(iterfile(), media_type="audio/mpeg")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Stream Error: {e}")
|
print(f"Stream Error: {e}")
|
||||||
|
# Return detail if it's a request error
|
||||||
|
if isinstance(e, requests.exceptions.HTTPError):
|
||||||
|
print(f"Upstream Status: {e.response.status_code}")
|
||||||
raise HTTPException(status_code=500, detail=str(e))
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
@router.get("/download")
|
@router.get("/download")
|
||||||
async def download_audio(id: str, title: str = "audio", yt: YouTubeService = Depends(get_youtube_service)):
|
async def download_audio(id: str, title: str = "audio", yt: YouTubeService = Depends(get_youtube_service)):
|
||||||
try:
|
try:
|
||||||
stream_url = yt.get_stream_url(id)
|
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():
|
def iterfile():
|
||||||
with requests.get(stream_url, stream=True) as r:
|
with requests.get(stream_url, headers=headers, stream=True, timeout=10) as r:
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
for chunk in r.iter_content(chunk_size=1024*1024):
|
for chunk in r.iter_content(chunk_size=1024*1024):
|
||||||
yield chunk
|
yield chunk
|
||||||
|
|
||||||
safe_filename = "".join([c for c in title if c.isalnum() or c in (' ', '-', '_')]).strip()
|
safe_filename = "".join([c for c in title if c.isalnum() or c in (' ', '-', '_')]).strip()
|
||||||
headers = {
|
final_headers = {
|
||||||
"Content-Disposition": f'attachment; filename="{safe_filename}.mp3"'
|
"Content-Disposition": f'attachment; filename="{safe_filename}.mp3"'
|
||||||
}
|
}
|
||||||
return StreamingResponse(iterfile(), media_type="audio/mpeg", headers=headers)
|
return StreamingResponse(iterfile(), media_type="audio/mpeg", headers=final_headers)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=500, detail=str(e))
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
|
||||||
|
|
@ -171,8 +171,14 @@ class YouTubeService:
|
||||||
stream_url = info.get('url')
|
stream_url = info.get('url')
|
||||||
|
|
||||||
if stream_url:
|
if stream_url:
|
||||||
self.cache.set(cache_key, stream_url, ttl_seconds=3600)
|
# Extract headers that yt-dlp used/recommends
|
||||||
return stream_url
|
headers = info.get('http_headers', {})
|
||||||
|
result = {
|
||||||
|
"url": stream_url,
|
||||||
|
"headers": headers
|
||||||
|
}
|
||||||
|
self.cache.set(cache_key, result, ttl_seconds=3600)
|
||||||
|
return result
|
||||||
raise ResourceNotFound("Stream not found")
|
raise ResourceNotFound("Stream not found")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ExternalAPIError(str(e))
|
raise ExternalAPIError(str(e))
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
services:
|
services:
|
||||||
spotify-clone:
|
spotify-clone:
|
||||||
image: vndangkhoa/spotify-clone:latest
|
image: git.khoavo.myds.me/vndangkhoa/spotify-clone:latest
|
||||||
container_name: spotify-clone
|
container_name: spotify-clone
|
||||||
restart: always
|
restart: always
|
||||||
network_mode: bridge # Synology often prefers explicit bridge or host
|
network_mode: bridge # Synology often prefers explicit bridge or host
|
||||||
|
|
@ -9,13 +9,3 @@ services:
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./data:/app/backend/data
|
- ./data:/app/backend/data
|
||||||
|
|
||||||
watchtower:
|
|
||||||
image: containrrr/watchtower
|
|
||||||
container_name: spotify-watchtower
|
|
||||||
restart: always
|
|
||||||
volumes:
|
|
||||||
- /var/run/docker.sock:/var/run/docker.sock
|
|
||||||
command: --interval 3600 --cleanup
|
|
||||||
environment:
|
|
||||||
- WATCHTOWER_INCLUDE_RESTARTING=true
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue