Skip to content

Commit d74eb76

Browse files
author
shiey
committed
🚀 Fix download functionality: Add streaming endpoint to bypass CORS issues
1 parent 7fdb3b4 commit d74eb76

4 files changed

Lines changed: 107 additions & 4 deletions

File tree

=3.8.0

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
Collecting aiohttp
2+
Downloading aiohttp-3.12.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (7.7 kB)
3+
Collecting aiohappyeyeballs>=2.5.0 (from aiohttp)
4+
Downloading aiohappyeyeballs-2.6.1-py3-none-any.whl.metadata (5.9 kB)
5+
Collecting aiosignal>=1.4.0 (from aiohttp)
6+
Downloading aiosignal-1.4.0-py3-none-any.whl.metadata (3.7 kB)
7+
Collecting attrs>=17.3.0 (from aiohttp)
8+
Downloading attrs-25.3.0-py3-none-any.whl.metadata (10 kB)
9+
Collecting frozenlist>=1.1.1 (from aiohttp)
10+
Downloading frozenlist-1.7.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (18 kB)
11+
Collecting multidict<7.0,>=4.5 (from aiohttp)
12+
Downloading multidict-6.6.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (5.3 kB)
13+
Collecting propcache>=0.2.0 (from aiohttp)
14+
Downloading propcache-0.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (12 kB)
15+
Collecting yarl<2.0,>=1.17.0 (from aiohttp)
16+
Downloading yarl-1.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (73 kB)
17+
━━━━━━━━━━━━━━━━━━━━ 73.9/73.9 kB 86.6 kB/s eta 0:00:00
18+
Requirement already satisfied: typing-extensions>=4.2 in ./venv/lib/python3.12/site-packages (from aiosignal>=1.4.0->aiohttp) (4.14.1)
19+
Requirement already satisfied: idna>=2.0 in ./venv/lib/python3.12/site-packages (from yarl<2.0,>=1.17.0->aiohttp) (3.10)
20+
Downloading aiohttp-3.12.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.7 MB)
21+
━━━━━━━━━━━━━━━━━━━━━━━━ 1.7/1.7 MB 68.0 kB/s eta 0:00:00
22+
Downloading aiohappyeyeballs-2.6.1-py3-none-any.whl (15 kB)
23+
Downloading aiosignal-1.4.0-py3-none-any.whl (7.5 kB)
24+
Downloading attrs-25.3.0-py3-none-any.whl (63 kB)
25+
━━━━━━━━━━━━━━━━━━━━━ 63.8/63.8 kB 113.2 kB/s eta 0:00:00
26+
Downloading frozenlist-1.7.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (241 kB)
27+
━━━━━━━━━━━━━━━━━━━━ 241.8/241.8 kB 74.1 kB/s eta 0:00:00
28+
Downloading multidict-6.6.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (256 kB)
29+
━━━━━━━━━━━━━━━━━━━━ 256.1/256.1 kB 76.5 kB/s eta 0:00:00
30+
Downloading propcache-0.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (224 kB)
31+
━━━━━━━━━━━━━━━━━━━━ 224.4/224.4 kB 60.8 kB/s eta 0:00:00
32+
Downloading yarl-1.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (355 kB)
33+
━━━━━━━━━━━━━━━━━━━━ 355.6/355.6 kB 64.6 kB/s eta 0:00:00
34+
Installing collected packages: propcache, multidict, frozenlist, attrs, aiohappyeyeballs, yarl, aiosignal, aiohttp
35+
Successfully installed aiohappyeyeballs-2.6.1 aiohttp-3.12.15 aiosignal-1.4.0 attrs-25.3.0 frozenlist-1.7.0 multidict-6.6.4 propcache-0.3.2 yarl-1.20.1

app/main.py

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
from fastapi import FastAPI, HTTPException, Depends, Request
22
from fastapi.middleware.cors import CORSMiddleware
3-
from fastapi.responses import JSONResponse
3+
from fastapi.responses import JSONResponse, StreamingResponse
44
from fastapi.staticfiles import StaticFiles
55
import logging
66
import sys
77
from contextlib import asynccontextmanager
8+
import aiohttp
9+
import tempfile
10+
import os
11+
from urllib.parse import urlparse
812

913
from app.config import settings
1014
from app.models import (
@@ -157,6 +161,53 @@ async def download_video(
157161
}
158162
)
159163

164+
# Streaming download endpoint
165+
@app.get("/stream/{video_id}")
166+
async def stream_video(video_id: str, url: str):
167+
"""
168+
Stream video file directly through our server to avoid CORS issues
169+
"""
170+
try:
171+
logger.info(f"Streaming video: {url}")
172+
173+
# Validate URL
174+
parsed_url = urlparse(url)
175+
if not parsed_url.scheme or not parsed_url.netloc:
176+
raise HTTPException(status_code=400, detail="Invalid URL")
177+
178+
async def generate():
179+
timeout = aiohttp.ClientTimeout(total=600) # 10 minutes timeout
180+
async with aiohttp.ClientSession(timeout=timeout) as session:
181+
try:
182+
async with session.get(
183+
url,
184+
headers={
185+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
186+
}
187+
) as response:
188+
if response.status != 200:
189+
raise HTTPException(status_code=response.status, detail="Failed to fetch video")
190+
191+
chunk_size = 8192
192+
async for chunk in response.content.iter_chunked(chunk_size):
193+
yield chunk
194+
195+
except Exception as e:
196+
logger.error(f"Streaming error: {str(e)}")
197+
raise
198+
199+
return StreamingResponse(
200+
generate(),
201+
media_type="video/mp4",
202+
headers={
203+
"Content-Disposition": f"attachment; filename={video_id}.mp4"
204+
}
205+
)
206+
207+
except Exception as e:
208+
logger.error(f"Stream error: {str(e)}")
209+
raise HTTPException(status_code=500, detail="Failed to stream video")
210+
160211
# Get video info without download
161212
@app.post("/info", response_model=VideoDownloadResponse)
162213
async def get_video_info(

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ fastapi==0.104.1
22
uvicorn[standard]==0.24.0
33
pydantic>=2.8.0
44
yt-dlp>=2025.8.22
5-
python-multipart==0.0.6
5+
python-multipart==0.0.6
6+
aiohttp>=3.8.0

static/script.js

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,15 @@ class FacebookVideoDownloader {
101101
</div>
102102
`;
103103

104-
// Set download link
105-
downloadLink.href = data.download_url;
104+
// Set download link with streaming endpoint
105+
const videoId = this.generateVideoId(data.video_info.title);
106+
const streamUrl = `/stream/${videoId}?url=${encodeURIComponent(data.download_url)}`;
107+
downloadLink.href = streamUrl;
106108
downloadLink.download = this.generateFileName(data.video_info.title);
107109

110+
// Remove the custom onclick handler - use native download
111+
downloadLink.onclick = null;
112+
108113
this.results.classList.remove('hidden');
109114
this.resetButton();
110115
}
@@ -162,6 +167,17 @@ class FacebookVideoDownloader {
162167

163168
return `${cleanTitle}.mp4`;
164169
}
170+
171+
generateVideoId(title) {
172+
if (!title) return 'facebook_video';
173+
174+
// Create a simple ID from title
175+
return title
176+
.replace(/[^\w\s-]/g, '') // Remove special characters
177+
.replace(/\s+/g, '_') // Replace spaces with underscores
178+
.toLowerCase()
179+
.substring(0, 30); // Limit length
180+
}
165181
}
166182

167183
// Initialize the application when DOM is loaded

0 commit comments

Comments
 (0)