Spaces:
Running
Running
from fastapi import FastAPI, File, UploadFile, Request | |
from fastapi.responses import HTMLResponse, JSONResponse, StreamingResponse | |
import requests | |
import time | |
from typing import Dict | |
app = FastAPI() | |
HTML_CONTENT = """ | |
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<title>Radd PRO Uploader</title> | |
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet"> | |
<style> | |
/* Global Styles */ | |
body { | |
font-family: 'Poppins', sans-serif; | |
background-color: #121212; | |
color: #e0e0e0; | |
margin: 0; | |
height: 100vh; | |
overflow: hidden; | |
} | |
/* Moving Grainy Background */ | |
body::before { | |
content: ""; | |
position: fixed; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAQAAACM/9unAAAACXBIWXMAAAsTAAALEwEAmpwYAAABF0lEQVR4nO3YMRLCQBRE0clsYgxZwwq7oLGxAgp2AtbsqQZsYgEG0wEWc/LSZnb9HcMjR8fHvEKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgsK8tJOq9fzs7vlkNlWvf5ELi+20t5k2VSL7p/iHJ5Rz5ynOhU7rQ4pUi+6b4hyeUc+cpzoVO60OkV7rQ4pUi+6b4hxenu+en4rQAOkaks/eJegGaUfOHI+nRvQCND1w2J9yf8C2KZwNjkeUsAKRa7Nz50tQAokVbO/U6AaiWjl5wd6kQAOke8Xpx3JgTQKVq7Heh0ADpFvF6cd0kE0Claux3odAA6RfxenHc+BNCpWrsd6HQAOkX8Xpx3PgTQqVq7Heh0ADpF/F6cdz4E0KlarjdI/cR1FBQUGBgYGBgYGBgYG/AT4AIHph6aQGU7oAAAAASUVORK5CYII=') repeat; | |
animation: grain 8s steps(10) infinite; | |
opacity: 0.2; | |
pointer-events: none; | |
} | |
@keyframes grain { | |
0% { transform: translate(0, 0); } | |
10% { transform: translate(-5%, -5%); } | |
20% { transform: translate(-10%, 5%); } | |
30% { transform: translate(5%, -10%); } | |
40% { transform: translate(-5%, 15%); } | |
50% { transform: translate(-10%, 5%); } | |
60% { transform: translate(15%, 0); } | |
70% { transform: translate(0, 10%); } | |
80% { transform: translate(-15%, 0); } | |
90% { transform: translate(10%, 5%); } | |
100% { transform: translate(5%, 0); } | |
} | |
.container { | |
position: relative; | |
max-width: 450px; | |
margin: 0 auto; | |
padding: 3rem 2rem; | |
background: rgba(18, 18, 18, 0.9); | |
backdrop-filter: blur(10px); | |
border-radius: 15px; | |
z-index: 1; | |
text-align: center; | |
box-shadow: 0 0 20px rgba(0, 0, 0, 0.8); | |
} | |
h1 { | |
margin-bottom: 2rem; | |
font-size: 2rem; | |
color: #ffffff; | |
text-shadow: 0 0 5px rgba(255, 255, 255, 0.2); | |
} | |
/* Buttons */ | |
.btn { | |
display: inline-block; | |
position: relative; | |
padding: 12px 24px; | |
margin: 0.5rem; | |
font-size: 1rem; | |
font-weight: 600; | |
color: #ffffff; | |
background-color: #2a2a2a; | |
border: none; | |
border-radius: 5px; | |
cursor: pointer; | |
overflow: hidden; | |
z-index: 1; | |
transition: color 0.3s ease, box-shadow 0.3s ease; | |
} | |
.btn:hover { | |
color: #ffffff; | |
box-shadow: 0 0 15px rgba(0, 122, 255, 0.5); | |
} | |
.btn:hover::before { | |
content: ''; | |
position: absolute; | |
inset: -10px; | |
background: radial-gradient(circle at center, rgba(0,122,255,0.2), transparent); | |
filter: blur(20px); | |
animation: glowAnimation 2s infinite; | |
z-index: -1; | |
pointer-events: none; | |
} | |
@keyframes glowAnimation { | |
0% { transform: scale(0.8); } | |
50% { transform: scale(1.2); } | |
100% { transform: scale(0.8); } | |
} | |
.btn:active { | |
transform: scale(0.98); | |
} | |
/* Small Buttons */ | |
.small-btn { | |
padding: 8px 16px; | |
font-size: 0.9rem; | |
font-weight: 500; | |
background-color: #2a2a2a; | |
color: #ffffff; | |
border: none; | |
border-radius: 5px; | |
cursor: pointer; | |
transition: color 0.3s ease, box-shadow 0.3s ease; | |
position: relative; | |
overflow: hidden; | |
z-index: 1; | |
} | |
.small-btn:hover { | |
color: #ffffff; | |
box-shadow: 0 0 10px rgba(0, 122, 255, 0.5); | |
} | |
.small-btn:hover::before { | |
content: ''; | |
position: absolute; | |
inset: -10px; | |
background: radial-gradient(circle at center, rgba(0,122,255,0.2), transparent); | |
filter: blur(15px); | |
animation: glowAnimation 2s infinite; | |
z-index: -1; | |
pointer-events: none; | |
} | |
.small-btn:active { | |
transform: scale(0.98); | |
} | |
/* Drop Zone */ | |
.drop-zone { | |
position: relative; | |
padding: 20px; | |
margin-bottom: 1rem; | |
border: 2px dashed #aaa; | |
border-radius: 10px; | |
cursor: pointer; | |
transition: all 0.3s ease; | |
background: rgba(255, 255, 255, 0.05); | |
} | |
.drop-zone:hover, .drop-zone.drag-over { | |
border-color: #ffffff; | |
background: rgba(255, 255, 255, 0.1); | |
position: relative; | |
} | |
.drop-zone:hover::before { | |
content: ''; | |
position: absolute; | |
inset: -10px; | |
background: radial-gradient(circle at center, rgba(0,122,255,0.2), transparent); | |
filter: blur(20px); | |
animation: glowAnimation 2s infinite; | |
z-index: -1; | |
pointer-events: none; | |
} | |
.file-input { | |
display: none; | |
} | |
.file-name { | |
margin-top: 1rem; | |
font-size: 0.9rem; | |
color: #aaa; | |
} | |
/* Progress Bar */ | |
.progress-container { | |
display: none; | |
margin-top: 1.5rem; | |
} | |
.progress-bar { | |
width: 100%; | |
height: 10px; | |
background-color: #333; | |
border-radius: 5px; | |
overflow: hidden; | |
margin-bottom: 10px; | |
} | |
.progress { | |
width: 0%; | |
height: 100%; | |
background-color: #ffffff; | |
transition: width 0.3s ease; | |
} | |
/* Loading Spinner */ | |
.loading-spinner { | |
display: none; | |
width: 40px; | |
height: 40px; | |
border: 4px solid #333; | |
border-top: 4px solid #ffffff; | |
border-radius: 50%; | |
animation: spin 1s linear infinite; | |
margin: 20px auto; | |
} | |
@keyframes spin { | |
0% { transform: rotate(0deg); } | |
100% { transform: rotate(360deg); } | |
} | |
/* Result Container */ | |
.result-container { | |
display: none; | |
margin-top: 1.5rem; | |
} | |
.result-link { | |
color: #ffffff; | |
text-decoration: none; | |
font-weight: 600; | |
transition: all 0.3s ease; | |
margin-right: 10px; | |
} | |
.result-link:hover { | |
text-decoration: underline; | |
} | |
/* File Types */ | |
.file-types { | |
margin-top: 2rem; | |
font-size: 0.8rem; | |
color: #aaa; | |
} | |
/* Modal Styles */ | |
.modal { | |
display: none; | |
position: fixed; | |
z-index: 2; | |
left: 0; | |
top: 0; | |
width: 100%; | |
height: 100%; | |
overflow: auto; | |
background-color: rgba(0,0,0,0.8); | |
} | |
.modal-content { | |
background-color: #1e1e1e; | |
margin: 15% auto; | |
padding: 20px; | |
border: 1px solid #333; | |
width: 80%; | |
max-width: 600px; | |
border-radius: 10px; | |
color: #e0e0e0; | |
animation: modalFadeIn 0.3s; | |
position: relative; | |
} | |
@keyframes modalFadeIn { | |
from {opacity: 0; transform: scale(0.8);} | |
to {opacity: 1; transform: scale(1);} | |
} | |
.close { | |
color: #aaa; | |
position: absolute; | |
top: 10px; | |
right: 15px; | |
font-size: 28px; | |
font-weight: bold; | |
transition: color 0.3s ease; | |
} | |
.close:hover, | |
.close:focus { | |
color: #fff; | |
text-decoration: none; | |
cursor: pointer; | |
} | |
.embed-container { | |
display: flex; | |
align-items: center; | |
margin-top: 15px; | |
} | |
#embedLink { | |
flex-grow: 1; | |
padding: 10px; | |
background-color: #333; | |
border: 1px solid #555; | |
color: #e0e0e0; | |
border-radius: 5px; | |
margin-right: 10px; | |
} | |
/* Media Queries */ | |
@media (max-width: 480px) { | |
.container { | |
padding: 2rem 1rem; | |
} | |
h1 { | |
font-size: 1.5rem; | |
} | |
.embed-container { | |
flex-direction: column; | |
} | |
#embedLink { | |
margin-bottom: 10px; | |
margin-right: 0; | |
} | |
} | |
</style> | |
</head> | |
<body> | |
<div class="container"> | |
<h1>Radd PRO Uploader</h1> | |
<form id="uploadForm"> | |
<div id="dropZone" class="drop-zone"> | |
<input type="file" name="file" id="file" class="file-input" accept=".zip,.mp4,.txt,.mp3,image/*,.pdf" required> | |
<label for="file" class="btn">Choose File</label> | |
<p>or drag and drop file here/paste image</p> | |
</div> | |
<div class="file-name" id="fileName"></div> | |
<button type="submit" id="uploadBtn" class="btn" style="display: none; margin-top: 1rem;">Upload File</button> | |
<div class="progress-container" id="progressContainer"></div> | |
<div class="loading-spinner" id="loadingSpinner"></div> | |
</form> | |
<div class="result-container" id="resultContainer"></div> | |
<div class="file-types"> | |
Allowed file types: .zip, .mp4, .txt, .mp3, all image types, .pdf | |
</div> | |
</div> | |
<div id="embedModal" class="modal"> | |
<div class="modal-content"> | |
<span class="close">×</span> | |
<h2>Embed Video Link</h2> | |
<p>Copy the following link to embed the video:</p> | |
<div class="embed-container"> | |
<input type="text" id="embedLink" readonly> | |
<button onclick="copyEmbedLink()" class="small-btn copy-embed-btn">Copy</button> | |
</div> | |
</div> | |
</div> | |
<script> | |
const fileInput = document.getElementById('file'); | |
const fileName = document.getElementById('fileName'); | |
const uploadForm = document.getElementById('uploadForm'); | |
const progressContainer = document.getElementById('progressContainer'); | |
const loadingSpinner = document.getElementById('loadingSpinner'); | |
const resultContainer = document.getElementById('resultContainer'); | |
const dropZone = document.getElementById('dropZone'); | |
const modal = document.getElementById('embedModal'); | |
const span = document.getElementsByClassName("close")[0]; | |
const embedLinkInput = document.getElementById('embedLink'); | |
fileInput.addEventListener('change', handleFileSelect); | |
uploadForm.addEventListener('submit', (e) => { | |
e.preventDefault(); | |
if (fileInput.files.length > 0) { | |
uploadFile(fileInput.files[0]); | |
} | |
}); | |
dropZone.addEventListener('dragover', (e) => { | |
e.preventDefault(); | |
dropZone.classList.add('drag-over'); | |
}); | |
dropZone.addEventListener('dragleave', () => { | |
dropZone.classList.remove('drag-over'); | |
}); | |
dropZone.addEventListener('drop', (e) => { | |
e.preventDefault(); | |
dropZone.classList.remove('drag-over'); | |
handleFileSelect({ target: { files: e.dataTransfer.files } }); | |
}); | |
document.addEventListener('paste', (e) => { | |
const items = e.clipboardData.items; | |
for (let i = 0; i < items.length; i++) { | |
if (items[i].kind === 'file') { | |
const file = items[i].getAsFile(); | |
handleFileSelect({ target: { files: [file] } }); | |
break; | |
} | |
} | |
}); | |
span.onclick = function() { | |
modal.style.display = "none"; | |
} | |
window.onclick = function(event) { | |
if (event.target == modal) { | |
modal.style.display = "none"; | |
} | |
} | |
function handleFileSelect(e) { | |
if (e.target.files.length > 0) { | |
const file = e.target.files[0]; | |
fileName.textContent = file.name; | |
document.getElementById('uploadBtn').style.display = 'inline-block'; | |
} | |
} | |
function uploadFile(file) { | |
progressContainer.innerHTML = ''; | |
progressContainer.style.display = 'block'; | |
loadingSpinner.style.display = 'block'; | |
document.getElementById('uploadBtn').disabled = true; | |
resultContainer.innerHTML = ''; | |
resultContainer.style.display = 'none'; | |
const progressBar = createProgressBar(file.name); | |
progressContainer.appendChild(progressBar); | |
const formData = new FormData(); | |
formData.append('file', file); | |
const xhr = new XMLHttpRequest(); | |
xhr.open('POST', '/upload', true); | |
xhr.upload.onprogress = (event) => updateProgress(event, progressBar.querySelector('.progress')); | |
xhr.onload = function() { | |
if (xhr.status === 200) { | |
const response = JSON.parse(xhr.responseText); | |
if (response.url) { | |
addResultLink(response.url, file.name); | |
} else { | |
alert('Upload failed: ' + response.error); | |
} | |
} else { | |
alert('Upload failed: ' + xhr.statusText); | |
} | |
resetUploadState(); | |
}; | |
xhr.onerror = function() { | |
alert('Upload failed: Network error'); | |
resetUploadState(); | |
}; | |
xhr.send(formData); | |
} | |
function createProgressBar(fileName) { | |
const progressBarContainer = document.createElement('div'); | |
progressBarContainer.className = 'progress-bar'; | |
const progress = document.createElement('div'); | |
progress.className = 'progress'; | |
progressBarContainer.appendChild(progress); | |
const label = document.createElement('div'); | |
label.textContent = fileName; | |
label.style.fontSize = '0.8rem'; | |
label.style.marginBottom = '5px'; | |
const container = document.createElement('div'); | |
container.appendChild(label); | |
container.appendChild(progressBarContainer); | |
return container; | |
} | |
function updateProgress(event, progressBar) { | |
if (event.lengthComputable) { | |
const percentComplete = (event.loaded / event.total) * 100; | |
progressBar.style.width = percentComplete + '%'; | |
} | |
} | |
function resetUploadState() { | |
fileInput.value = ''; | |
fileName.textContent = ''; | |
document.getElementById('uploadBtn').style.display = 'none'; | |
document.getElementById('uploadBtn').disabled = false; | |
loadingSpinner.style.display = 'none'; | |
} | |
function addResultLink(url, fileName) { | |
const linkContainer = document.createElement('div'); | |
linkContainer.style.marginBottom = '10px'; | |
const link = document.createElement('a'); | |
link.href = url; | |
link.textContent = `View ${fileName}`; | |
link.className = 'result-link'; | |
link.target = '_blank'; | |
const copyBtn = document.createElement('button'); | |
copyBtn.textContent = 'Copy Link'; | |
copyBtn.className = 'small-btn copy-btn'; | |
copyBtn.onclick = () => { | |
navigator.clipboard.writeText(window.location.origin + url).then(() => { | |
alert('Link copied to clipboard!'); | |
}); | |
}; | |
linkContainer.appendChild(link); | |
linkContainer.appendChild(copyBtn); | |
if (fileName.toLowerCase().endsWith('.mp4')) { | |
const embedBtn = document.createElement('button'); | |
embedBtn.textContent = 'Embed Video for Discord'; | |
embedBtn.className = 'small-btn embed-btn'; | |
embedBtn.onclick = () => { | |
showEmbedModal(url); | |
}; | |
linkContainer.appendChild(embedBtn); | |
} | |
resultContainer.appendChild(linkContainer); | |
resultContainer.style.display = 'block'; | |
} | |
function showEmbedModal(url) { | |
const embedUrl = `${window.location.origin}/embed?url=${encodeURIComponent(window.location.origin + url)}&thumbnail=${encodeURIComponent('https://example.com/thumbnail.png')}`; | |
embedLinkInput.value = embedUrl; | |
modal.style.display = "block"; | |
} | |
function copyEmbedLink() { | |
embedLinkInput.select(); | |
document.execCommand('copy'); | |
alert('Embed link copied to clipboard!'); | |
} | |
</script> | |
</body> | |
</html> | |
""" | |
async def index(): | |
return HTML_CONTENT | |
async def handle_upload(file: UploadFile = File(...)): | |
if not file.filename: | |
return JSONResponse(content={"error": "No file selected."}, status_code=400) | |
cookies = await get_cookies() | |
if 'csrftoken' not in cookies or 'sessionid' not in cookies: | |
return JSONResponse(content={"error": "Failed to obtain necessary cookies"}, status_code=500) | |
upload_result = await initiate_upload(cookies, file.filename, file.content_type) | |
if not upload_result or 'upload_url' not in upload_result: | |
return JSONResponse(content={"error": "Failed to initiate upload"}, status_code=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 JSONResponse(content={"error": "File upload failed after multiple attempts"}, status_code=500) | |
original_url = upload_result['serving_url'] | |
mirrored_url = f"/rbxg/{original_url.split('/pbxt/')[1]}" | |
return JSONResponse(content={"url": mirrored_url}) | |
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'] = '*' | |
headers['Content-Disposition'] = 'inline' | |
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 embed_video(url: str, thumbnail: str): | |
html = f''' | |
<html> | |
<head> | |
<meta property="og:type" content="video.other"> | |
<meta property="og:video" content="{url}"> | |
<meta property="og:video:url" content="{url}"> | |
<meta property="og:video:secure_url" content="{url}"> | |
<meta property="og:video:type" content="video/mp4"> | |
<meta property="og:video:width" content="1280"> | |
<meta property="og:video:height" content="720"> | |
<meta property="og:image" content="{thumbnail}"> | |
<meta property="og:image:secure_url" content="{thumbnail}"> | |
<meta property="og:image:width" content="1280"> | |
<meta property="og:image:height" content="720"> | |
<meta property="og:image:type" content="image/png"> | |
<style> | |
body, html {{ margin: 0; padding: 0; height: 100%; background: #000; }} | |
#thumbnail {{ width: 100%; height: 100%; object-fit: contain; cursor: pointer; }} | |
#video {{ display: none; width: 100%; height: 100%; object-fit: contain; }} | |
</style> | |
</head> | |
<body> | |
<img id="thumbnail" src="{thumbnail}" onclick="playVideo()"> | |
<video id="video" controls autoplay> | |
<source src="{url}" type="video/mp4"> | |
Your browser does not support the video tag. | |
</video> | |
<script> | |
function playVideo() {{ | |
document.getElementById('thumbnail').style.display = 'none'; | |
document.getElementById('video').style.display = 'block'; | |
document.getElementById('video').play(); | |
}} | |
</script> | |
</body> | |
</html> | |
''' | |
return HTMLResponse(content=html) | |
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' | |
}) | |
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}) | |
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): | |
success = await upload_file(upload_url, file_content, content_type) | |
if success: | |
return True | |
if attempt < max_retries: | |
time.sleep(delay) | |
delay *= 2 # Exponential backoff | |
return False |