commit eb40c9c5738b4afb430a0b109f771c1f232b3a5e
parent 44d3b81a84a2b9406ccec7f71045a780b31e0cae
Author: Amit Dutta <mail@amit.is-a.dev>
Date: Thu, 3 Sep 2026 15:16:49 +0530
Merge pull request #1 from notamitgamer/patch
Refactor streaming response to use async generator
Diffstat:
2 files changed, 32 insertions(+), 6 deletions(-)
diff --git a/app/main.py b/app/main.py
@@ -35,6 +35,7 @@ async def stream_raw(path: str):
r = await client.send(req, stream=True)
if r.status_code != 200:
+ await client.aclose()
raise HTTPException(status_code=404, detail="File not found")
headers = {
@@ -42,7 +43,15 @@ async def stream_raw(path: str):
"Cache-Control": "public, max-age=31536000",
"Content-Type": r.headers.get("Content-Type", "application/octet-stream")
}
- return StreamingResponse(r.aiter_raw(), headers=headers)
+
+ async def stream_generator():
+ try:
+ async for chunk in r.aiter_raw():
+ yield chunk
+ finally:
+ await client.aclose()
+
+ return StreamingResponse(stream_generator(), headers=headers)
@app.get("/api/download/{path:path}")
async def download_file(path: str):
@@ -53,6 +62,7 @@ async def download_file(path: str):
r = await client.send(req, stream=True)
if r.status_code != 200:
+ await client.aclose()
raise HTTPException(status_code=404, detail="Not found")
filename = path.split("/")[-1]
@@ -60,7 +70,15 @@ async def download_file(path: str):
"Content-Disposition": f'attachment; filename="{filename}"',
"Content-Type": r.headers.get("Content-Type", "application/octet-stream")
}
- return StreamingResponse(r.aiter_raw(), headers=headers)
+
+ async def stream_generator():
+ try:
+ async for chunk in r.aiter_raw():
+ yield chunk
+ finally:
+ await client.aclose()
+
+ return StreamingResponse(stream_generator(), headers=headers)
# Phase 4: Token-protected Upload endpoint
@app.post("/api/upload")
@@ -124,4 +142,4 @@ async def serve(request: Request, path: str):
"page": "listing",
"path": clean_path,
"items": items
- })-
\ No newline at end of file
+ })
diff --git a/app/ytmusic.py b/app/ytmusic.py
@@ -6,6 +6,15 @@ from fastapi import BackgroundTasks
from fastapi.responses import FileResponse
# Phase 5: YouTube Music processing
+def filter_duration_and_live(info, *, incomplete):
+ """Reject live streams and videos over 20 minutes to save Render CPU/resources."""
+ duration = info.get('duration')
+ if duration and duration > 1200:
+ return 'Video is too long (max 20 minutes)'
+ if info.get('is_live'):
+ return 'Live streams are not supported'
+ return None
+
def process_and_stream_ytmusic(url: str, background_tasks: BackgroundTasks):
temp_dir = tempfile.mkdtemp()
@@ -17,6 +26,7 @@ def process_and_stream_ytmusic(url: str, background_tasks: BackgroundTasks):
'audio_quality': '0',
'outtmpl': os.path.join(temp_dir, '%(artist)s - %(title)s.%(ext)s'),
'noplaylist': True,
+ 'match_filter': filter_duration_and_live,
}
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
@@ -44,4 +54,4 @@ def process_and_stream_ytmusic(url: str, background_tasks: BackgroundTasks):
)
except Exception as e:
shutil.rmtree(temp_dir, ignore_errors=True)
- raise e-
\ No newline at end of file
+ raise e