Spaces:
Running
Running
from fastapi import FastAPI, File, UploadFile, Request | |
from fastapi.responses import HTMLResponse, RedirectResponse, StreamingResponse | |
import requests | |
from urllib.parse import urljoin | |
import time | |
from typing import Dict | |
app = FastAPI() | |
HTML_CONTENT = """ | |
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Pro File Uploader</title> | |
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;700&display=swap" rel="stylesheet"> | |
<style> | |
body { | |
font-family: 'Roboto', sans-serif; | |
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
height: 100vh; | |
margin: 0; | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
} | |
.container { | |
background: rgba(255, 255, 255, 0.9); | |
padding: 2rem; | |
border-radius: 10px; | |
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
text-align: center; | |
max-width: 400px; | |
width: 100%; | |
} | |
h1 { | |
color: #4a4a4a; | |
margin-bottom: 1.5rem; | |
} | |
.file-input { | |
display: none; | |
} | |
.file-label { | |
background-color: #3498db; | |
color: white; | |
padding: 10px 20px; | |
border-radius: 5px; | |
cursor: pointer; | |
transition: background-color 0.3s ease; | |
} | |
.file-label:hover { | |
background-color: #2980b9; | |
} | |
.file-name { | |
margin-top: 1rem; | |
font-size: 0.9rem; | |
color: #666; | |
} | |
.progress-bar { | |
width: 100%; | |
height: 10px; | |
background-color: #e0e0e0; | |
border-radius: 5px; | |
margin-top: 1rem; | |
overflow: hidden; | |
display: none; | |
} | |
.progress { | |
width: 0%; | |
height: 100%; | |
background-color: #2ecc71; | |
transition: width 0.5s ease; | |
} | |
@keyframes pulse { | |
0% { transform: scale(1); } | |
50% { transform: scale(1.05); } | |
100% { transform: scale(1); } | |
} | |
.pulse { | |
animation: pulse 2s infinite; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="container"> | |
<h1>Pro File Uploader</h1> | |
<form id="uploadForm" action="/upload" method="post" enctype="multipart/form-data"> | |
<input type="file" name="file" id="file" class="file-input" accept="*/*" required> | |
<label for="file" class="file-label">Choose File</label> | |
<div class="file-name" id="fileName"></div> | |
<div class="progress-bar" id="progressBar"> | |
<div class="progress" id="progress"></div> | |
</div> | |
</form> | |
</div> | |
<script> | |
const fileInput = document.getElementById('file'); | |
const fileName = document.getElementById('fileName'); | |
const uploadForm = document.getElementById('uploadForm'); | |
const progressBar = document.getElementById('progressBar'); | |
const progress = document.getElementById('progress'); | |
fileInput.addEventListener('change', (e) => { | |
if (e.target.files.length > 0) { | |
fileName.textContent = e.target.files[0].name; | |
uploadForm.submit(); | |
showProgress(); | |
} | |
}); | |
function showProgress() { | |
progressBar.style.display = 'block'; | |
let width = 0; | |
const interval = setInterval(() => { | |
if (width >= 100) { | |
clearInterval(interval); | |
} else { | |
width++; | |
progress.style.width = width + '%'; | |
} | |
}, 20); | |
} | |
document.querySelector('.file-label').classList.add('pulse'); | |
</script> | |
</body> | |
</html> | |
""" | |
async def index(): | |
return HTML_CONTENT | |
async def handle_upload(file: UploadFile = File(...)): | |
if not file.filename: | |
return {"error": "No file selected."}, 400 | |
cookies = await get_cookies() | |
if 'csrftoken' not in cookies or 'sessionid' not in cookies: | |
return {"error": "Failed to obtain necessary cookies"}, 500 | |
upload_result = await initiate_upload(cookies, file.filename, file.content_type) | |
if not upload_result or 'upload_url' not in upload_result: | |
return {"error": "Failed to initiate upload"}, 500 | |
file_content = await file.read() | |
upload_success = await retry_upload(upload_result['upload_url'], file_content, file.content_type) | |
if not upload_success: | |
return {"error": "File upload failed after multiple attempts"}, 500 | |
original_url = upload_result['serving_url'] | |
mirrored_url = f"/rbxg/{original_url.split('/pbxt/')[1]}" | |
return RedirectResponse(url=mirrored_url, status_code=302) | |
async def handle_video_stream(path: str, request: Request): | |
original_url = f'https://replicate.delivery/pbxt/{path}' | |
range_header = request.headers.get('Range') | |
headers = {'Range': range_header} if range_header else {} | |
response = requests.get(original_url, headers=headers, stream=True) | |
def generate(): | |
for chunk in response.iter_content(chunk_size=8192): | |
yield chunk | |
headers = dict(response.headers) | |
headers['Access-Control-Allow-Origin'] = '*' | |
if response.status_code == 206: | |
headers['Content-Range'] = response.headers.get('Content-Range') | |
return StreamingResponse(generate(), status_code=response.status_code, headers=headers) | |
async def get_cookies() -> Dict[str, str]: | |
try: | |
response = requests.get('https://replicate.com/levelsio/neon-tokyo', headers={ | |
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36' | |
}) | |
return dict(response.cookies) | |
except Exception as e: | |
print(f'Error fetching the page: {e}') | |
return {} | |
async def initiate_upload(cookies: Dict[str, str], filename: str, content_type: str) -> Dict: | |
url = f'https://replicate.com/api/upload/{filename}?content_type={content_type}' | |
try: | |
response = requests.post(url, cookies=cookies, headers={ | |
'X-CSRFToken': cookies.get('csrftoken'), | |
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36', | |
'Referer': 'https://replicate.com/levelsio/neon-tokyo', | |
'Origin': 'https://replicate.com', | |
'Accept': '*/*', | |
'Accept-Language': 'en-US,en;q=0.5', | |
'Accept-Encoding': 'identity', | |
'Sec-Fetch-Dest': 'empty', | |
'Sec-Fetch-Mode': 'cors', | |
'Sec-Fetch-Site': 'same-origin', | |
'Sec-GPC': '1', | |
'Priority': 'u=1, i' | |
}) | |
print(f'Initiate upload response status: {response.status_code}') | |
print(f'Initiate upload response headers: {response.headers}') | |
print(f'Response body: {response.text}') | |
return response.json() | |
except Exception as e: | |
print(f'Error initiating upload: {e}') | |
raise | |
async def upload_file(upload_url: str, file_content: bytes, content_type: str) -> bool: | |
try: | |
response = requests.put(upload_url, data=file_content, headers={'Content-Type': content_type}) | |
print(f'File upload response status: {response.status_code}') | |
print(f'File upload response headers: {response.headers}') | |
return response.status_code == 200 | |
except Exception as e: | |
print(f'Error uploading file: {e}') | |
return False | |
async def retry_upload(upload_url: str, file_content: bytes, content_type: str, max_retries: int = 5, delay: int = 1) -> bool: | |
for attempt in range(1, max_retries + 1): | |
print(f'Upload attempt {attempt} of {max_retries}') | |
success = await upload_file(upload_url, file_content, content_type) | |
if success: | |
print('Upload successful') | |
return True | |
if attempt < max_retries: | |
print(f'Upload failed, retrying in {delay}s...') | |
time.sleep(delay) | |
delay *= 2 # Exponential backoff | |
print('Upload failed after all retry attempts') | |
return False |