diff --git a/backend/api/endpoints/stream.py b/backend/api/endpoints/stream.py index 2498383..1da80be 100644 --- a/backend/api/endpoints/stream.py +++ b/backend/api/endpoints/stream.py @@ -11,10 +11,19 @@ def get_youtube_service(): @router.get("/stream") async def stream_audio(id: str, yt: YouTubeService = Depends(get_youtube_service)): 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(): - 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() for chunk in r.iter_content(chunk_size=64*1024): 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") except Exception as 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)) @router.get("/download") async def download_audio(id: str, title: str = "audio", yt: YouTubeService = Depends(get_youtube_service)): 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(): - 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() 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() - headers = { + final_headers = { "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: raise HTTPException(status_code=500, detail=str(e)) diff --git a/backend/services/youtube.py b/backend/services/youtube.py index 9974ec1..b2186b8 100644 --- a/backend/services/youtube.py +++ b/backend/services/youtube.py @@ -171,8 +171,14 @@ class YouTubeService: stream_url = info.get('url') if stream_url: - self.cache.set(cache_key, stream_url, ttl_seconds=3600) - return stream_url + # Extract headers that yt-dlp used/recommends + 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") except Exception as e: raise ExternalAPIError(str(e)) diff --git a/docker-compose.yml b/docker-compose.yml index a252be6..73dc99b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,6 @@ services: spotify-clone: - image: vndangkhoa/spotify-clone:latest + image: git.khoavo.myds.me/vndangkhoa/spotify-clone:latest container_name: spotify-clone restart: always network_mode: bridge # Synology often prefers explicit bridge or host @@ -9,13 +9,3 @@ services: volumes: - ./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