fix: improve stream API for Docker/NAS - add SSL certs, retries, geo bypass
This commit is contained in:
parent
50248cf165
commit
7bb58693dd
2 changed files with 33 additions and 19 deletions
|
|
@ -1,10 +1,12 @@
|
||||||
FROM python:3.11-slim
|
FROM python:3.11-slim
|
||||||
|
|
||||||
# Install Node.js
|
# Install Node.js and dependencies
|
||||||
RUN apt-get update && apt-get install -y \
|
RUN apt-get update && apt-get install -y \
|
||||||
curl \
|
curl \
|
||||||
gnupg \
|
gnupg \
|
||||||
ffmpeg \
|
ffmpeg \
|
||||||
|
ca-certificates \
|
||||||
|
&& update-ca-certificates \
|
||||||
&& curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
|
&& curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
|
||||||
&& apt-get install -y nodejs \
|
&& apt-get install -y nodejs \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
|
||||||
|
|
@ -615,15 +615,24 @@ async def stream_audio(id: str):
|
||||||
print(f"DEBUG: Fetching new stream URL for '{id}'")
|
print(f"DEBUG: Fetching new stream URL for '{id}'")
|
||||||
url = f"https://www.youtube.com/watch?v={id}"
|
url = f"https://www.youtube.com/watch?v={id}"
|
||||||
ydl_opts = {
|
ydl_opts = {
|
||||||
'format': 'bestaudio[ext=m4a]/best[ext=mp4]/best', # Prefer m4a/aac for iOS
|
'format': 'bestaudio[ext=m4a]/bestaudio[ext=webm]/bestaudio/best',
|
||||||
'quiet': True,
|
'quiet': True,
|
||||||
'noplaylist': True,
|
'noplaylist': True,
|
||||||
|
'nocheckcertificate': True, # Bypass SSL issues in Docker
|
||||||
|
'geo_bypass': True, # Bypass geo restrictions
|
||||||
|
'socket_timeout': 30, # Timeout for sockets
|
||||||
|
'retries': 3, # Retry on transient errors
|
||||||
}
|
}
|
||||||
|
|
||||||
# Extract direct URL
|
# Extract direct URL
|
||||||
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
try:
|
||||||
info = ydl.extract_info(url, download=False)
|
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
||||||
stream_url = info.get('url')
|
info = ydl.extract_info(url, download=False)
|
||||||
|
stream_url = info.get('url')
|
||||||
|
print(f"DEBUG: Got stream URL format: {info.get('format')}, ext: {info.get('ext')}")
|
||||||
|
except Exception as ydl_error:
|
||||||
|
print(f"DEBUG: yt-dlp extraction error: {type(ydl_error).__name__}: {str(ydl_error)}")
|
||||||
|
raise ydl_error
|
||||||
|
|
||||||
if stream_url:
|
if stream_url:
|
||||||
# Cache for 1 hour (3600 seconds) - URLs expire
|
# Cache for 1 hour (3600 seconds) - URLs expire
|
||||||
|
|
@ -634,24 +643,27 @@ async def stream_audio(id: str):
|
||||||
|
|
||||||
# Stream the content
|
# Stream the content
|
||||||
def iterfile():
|
def iterfile():
|
||||||
# Verify if URL is still valid by making a HEAD request or handling stream error
|
try:
|
||||||
# For simplicity, we just try to stream. If 403, we might need to invalidate,
|
with requests.get(stream_url, stream=True, timeout=30) as r:
|
||||||
# but that logic is complex for this method.
|
r.raise_for_status()
|
||||||
with requests.get(stream_url, stream=True) as r:
|
for chunk in r.iter_content(chunk_size=64*1024):
|
||||||
r.raise_for_status() # Check for 403
|
yield chunk
|
||||||
# Use smaller chunks (64KB) for better TTFB (Time To First Byte)
|
except requests.exceptions.HTTPError as http_err:
|
||||||
for chunk in r.iter_content(chunk_size=64*1024):
|
print(f"DEBUG: Stream HTTP Error: {http_err}")
|
||||||
yield chunk
|
# Invalidate cache on 403
|
||||||
|
if http_err.response.status_code == 403:
|
||||||
|
cache.delete(cache_key)
|
||||||
|
raise
|
||||||
|
|
||||||
# Note: We return audio/mpeg, but it might be opus/webm.
|
|
||||||
# Browsers are usually smart enough to sniff.
|
|
||||||
return StreamingResponse(iterfile(), media_type="audio/mpeg")
|
return StreamingResponse(iterfile(), media_type="audio/mpeg")
|
||||||
|
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Stream Error: {e}")
|
import traceback
|
||||||
# If cached URL failed (likely 403), we could try to invalidate here,
|
print(f"Stream Error for ID '{id}': {type(e).__name__}: {str(e)}")
|
||||||
# but for now we just return error.
|
print(traceback.format_exc())
|
||||||
raise HTTPException(status_code=500, detail=str(e))
|
raise HTTPException(status_code=500, detail=f"Stream error: {type(e).__name__}: {str(e)}")
|
||||||
|
|
||||||
@router.get("/download")
|
@router.get("/download")
|
||||||
async def download_audio(id: str, title: str = "audio"):
|
async def download_audio(id: str, title: str = "audio"):
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue