Update app.py
Browse files
app.py
CHANGED
|
@@ -5,9 +5,11 @@ import asyncio
|
|
| 5 |
from typing import Dict
|
| 6 |
import os
|
| 7 |
import mimetypes
|
|
|
|
| 8 |
|
| 9 |
app = FastAPI()
|
| 10 |
|
|
|
|
| 11 |
HTML_CONTENT = """
|
| 12 |
<!DOCTYPE html>
|
| 13 |
<html lang="en">
|
|
@@ -697,45 +699,46 @@ HTML_CONTENT = """
|
|
| 697 |
const formData = new FormData();
|
| 698 |
formData.append('file', file);
|
| 699 |
|
| 700 |
-
|
| 701 |
-
|
| 702 |
-
|
| 703 |
-
|
| 704 |
-
|
| 705 |
-
|
| 706 |
-
|
| 707 |
-
|
| 708 |
-
|
| 709 |
-
if (response.url) {
|
| 710 |
-
addResultLink(response.url, file.name, response.originalExtension);
|
| 711 |
-
saveToHistory(file.name, response.url, response.originalExtension);
|
| 712 |
-
resetUploadState();
|
| 713 |
-
return;
|
| 714 |
-
} else {
|
| 715 |
-
throw new Error('Upload failed: ' + response.error);
|
| 716 |
-
}
|
| 717 |
-
} else {
|
| 718 |
-
throw new Error(`HTTP error! status: ${xhr.status}`);
|
| 719 |
-
}
|
| 720 |
-
};
|
| 721 |
-
|
| 722 |
-
xhr.onerror = function() {
|
| 723 |
-
throw new Error('Network error occurred');
|
| 724 |
-
};
|
| 725 |
|
| 726 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 727 |
|
| 728 |
-
|
| 729 |
-
|
| 730 |
-
|
| 731 |
-
|
|
|
|
| 732 |
|
| 733 |
-
|
| 734 |
-
} catch (error) {
|
| 735 |
-
console.error('Upload error:', error);
|
| 736 |
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
| 737 |
-
}
|
| 738 |
-
}
|
| 739 |
}
|
| 740 |
|
| 741 |
function createProgressBar(fileName) {
|
|
@@ -754,11 +757,8 @@ HTML_CONTENT = """
|
|
| 754 |
return container;
|
| 755 |
}
|
| 756 |
|
| 757 |
-
function updateProgress(
|
| 758 |
-
|
| 759 |
-
const percentComplete = (event.loaded / event.total) * 100;
|
| 760 |
-
progressBar.style.width = percentComplete + '%';
|
| 761 |
-
}
|
| 762 |
}
|
| 763 |
|
| 764 |
function resetUploadState() {
|
|
@@ -946,12 +946,12 @@ async def handle_upload(file: UploadFile = File(...)):
|
|
| 946 |
|
| 947 |
cookies = await get_cookies()
|
| 948 |
if 'csrftoken' not in cookies or 'sessionid' not in cookies:
|
| 949 |
-
return JSONResponse(content={"error": "Failed to get cookies from Replicate."}, status_code=500)
|
| 950 |
|
| 951 |
-
original_extension = os.path.splitext(file.filename)[1][1:]
|
| 952 |
supported_types = ['mp4', 'png', 'jpg', 'jpeg', 'gif', 'mp3', 'pdf', 'txt']
|
| 953 |
|
| 954 |
-
if original_extension
|
| 955 |
temp_filename = file.filename
|
| 956 |
content_type = file.content_type
|
| 957 |
else:
|
|
@@ -960,29 +960,32 @@ async def handle_upload(file: UploadFile = File(...)):
|
|
| 960 |
|
| 961 |
upload_result = await initiate_upload(cookies, temp_filename, content_type)
|
| 962 |
if not upload_result or 'upload_url' not in upload_result:
|
| 963 |
-
return JSONResponse(content={"error": "Failed to initiate upload with Replicate."}, status_code=500)
|
| 964 |
|
| 965 |
-
# Log the real Replicate URL
|
| 966 |
-
print(f"Real Replicate URL: {upload_result
|
| 967 |
|
| 968 |
file_content = await file.read()
|
| 969 |
upload_success = await retry_upload(upload_result['upload_url'], file_content, content_type)
|
| 970 |
if not upload_success:
|
| 971 |
-
return JSONResponse(content={"error": "File upload to Replicate failed after multiple attempts."}, status_code=500)
|
| 972 |
|
| 973 |
original_url = upload_result['serving_url']
|
| 974 |
mirrored_url = f"/upload/{original_url.split('/pbxt/')[1]}"
|
| 975 |
|
| 976 |
-
if original_extension
|
|
|
|
| 977 |
mirrored_url = mirrored_url.replace('.png', '')
|
| 978 |
|
| 979 |
return JSONResponse(content={"url": mirrored_url, "originalExtension": original_extension})
|
| 980 |
|
|
|
|
| 981 |
@app.get("/upload/{path:path}")
|
| 982 |
async def handle_file_stream(path: str, request: Request):
|
| 983 |
original_url = f'https://replicate.delivery/pbxt/{path}'
|
| 984 |
|
| 985 |
-
|
|
|
|
| 986 |
original_url += '.png'
|
| 987 |
|
| 988 |
range_header = request.headers.get('Range')
|
|
@@ -1003,6 +1006,7 @@ async def handle_file_stream(path: str, request: Request):
|
|
| 1003 |
if response.status_code == 206:
|
| 1004 |
response_headers['Content-Range'] = response.headers.get('Content-Range')
|
| 1005 |
|
|
|
|
| 1006 |
original_extension = os.path.splitext(path)[1][1:]
|
| 1007 |
content_type, _ = mimetypes.guess_type(f"file.{original_extension}")
|
| 1008 |
if content_type:
|
|
@@ -1014,7 +1018,8 @@ async def handle_file_stream(path: str, request: Request):
|
|
| 1014 |
|
| 1015 |
|
| 1016 |
@app.get("/embed")
|
| 1017 |
-
async def embed_video(url: str, thumbnail: str):
|
|
|
|
| 1018 |
html = f'''
|
| 1019 |
<html>
|
| 1020 |
<head>
|
|
@@ -1032,80 +1037,81 @@ async def embed_video(url: str, thumbnail: str):
|
|
| 1032 |
<meta property="og:image:type" content="image/png">
|
| 1033 |
<style>
|
| 1034 |
body, html {{ margin: 0; padding: 0; height: 100%; background: #000; }}
|
| 1035 |
-
|
| 1036 |
-
#video {{ display: none; width: 100%; height: 100%; object-fit: contain; }}
|
| 1037 |
</style>
|
| 1038 |
</head>
|
| 1039 |
<body>
|
| 1040 |
-
<
|
| 1041 |
-
<video id="video" controls autoplay>
|
| 1042 |
<source src="{url}" type="video/mp4">
|
| 1043 |
Your browser does not support the video tag.
|
| 1044 |
</video>
|
| 1045 |
-
<script>
|
| 1046 |
-
function playVideo() {{
|
| 1047 |
-
document.getElementById('thumbnail').style.display = 'none';
|
| 1048 |
-
document.getElementById('video').style.display = 'block';
|
| 1049 |
-
document.getElementById('video').play();
|
| 1050 |
-
}}
|
| 1051 |
-
</script>
|
| 1052 |
</body>
|
| 1053 |
</html>
|
| 1054 |
'''
|
| 1055 |
return HTMLResponse(content=html)
|
| 1056 |
|
|
|
|
| 1057 |
async def get_cookies() -> Dict[str, str]:
|
| 1058 |
try:
|
| 1059 |
-
response = requests.get('https://replicate.com/
|
| 1060 |
'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'
|
| 1061 |
})
|
| 1062 |
response.raise_for_status()
|
| 1063 |
return dict(response.cookies)
|
| 1064 |
except requests.exceptions.RequestException as e:
|
| 1065 |
-
print(f'Error fetching
|
| 1066 |
return {}
|
| 1067 |
|
|
|
|
| 1068 |
async def initiate_upload(cookies: Dict[str, str], filename: str, content_type: str) -> Dict:
|
| 1069 |
url = f'https://replicate.com/api/upload/{filename}?content_type={content_type}'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1070 |
try:
|
| 1071 |
-
response = requests.post(url, cookies=cookies, headers=
|
| 1072 |
-
'X-CSRFToken': cookies.get('csrftoken'),
|
| 1073 |
-
'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',
|
| 1074 |
-
'Referer': 'https://replicate.com/levelsio/neon-tokyo',
|
| 1075 |
-
'Origin': 'https://replicate.com',
|
| 1076 |
-
'Accept': '*/*',
|
| 1077 |
-
'Accept-Language': 'en-US,en;q=0.5',
|
| 1078 |
-
'Accept-Encoding': 'gzip, deflate, br',
|
| 1079 |
-
'Sec-Fetch-Dest': 'empty',
|
| 1080 |
-
'Sec-Fetch-Mode': 'cors',
|
| 1081 |
-
'Sec-Fetch-Site': 'same-origin',
|
| 1082 |
-
})
|
| 1083 |
response.raise_for_status()
|
| 1084 |
return response.json()
|
| 1085 |
-
except requests.exceptions.
|
| 1086 |
-
print(f'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1087 |
return {}
|
| 1088 |
|
|
|
|
| 1089 |
async def upload_file_to_gcs(upload_url: str, file_content: bytes, content_type: str) -> bool:
|
| 1090 |
try:
|
| 1091 |
response = requests.put(upload_url, data=file_content, headers={'Content-Type': content_type})
|
| 1092 |
response.raise_for_status()
|
| 1093 |
return response.status_code == 200
|
| 1094 |
except requests.exceptions.RequestException as e:
|
| 1095 |
-
print(f'Error
|
| 1096 |
return False
|
| 1097 |
|
| 1098 |
-
|
|
|
|
| 1099 |
for attempt in range(max_retries):
|
| 1100 |
-
|
| 1101 |
-
|
| 1102 |
-
|
| 1103 |
-
|
| 1104 |
-
print(f"Upload attempt {attempt + 1} failed. Retrying in {delay} seconds...")
|
| 1105 |
-
except Exception as e:
|
| 1106 |
-
print(f"Error during upload attempt {attempt + 1}: {e}")
|
| 1107 |
-
|
| 1108 |
await asyncio.sleep(delay)
|
| 1109 |
-
delay
|
| 1110 |
-
|
| 1111 |
return False
|
|
|
|
| 5 |
from typing import Dict
|
| 6 |
import os
|
| 7 |
import mimetypes
|
| 8 |
+
import json
|
| 9 |
|
| 10 |
app = FastAPI()
|
| 11 |
|
| 12 |
+
# The HTML_CONTENT remains the same as your provided version.
|
| 13 |
HTML_CONTENT = """
|
| 14 |
<!DOCTYPE html>
|
| 15 |
<html lang="en">
|
|
|
|
| 699 |
const formData = new FormData();
|
| 700 |
formData.append('file', file);
|
| 701 |
|
| 702 |
+
const xhr = new XMLHttpRequest();
|
| 703 |
+
xhr.open('POST', '/upload', true);
|
| 704 |
+
|
| 705 |
+
xhr.upload.onprogress = (event) => {
|
| 706 |
+
if (event.lengthComputable) {
|
| 707 |
+
const percentComplete = (event.loaded / event.total) * 100;
|
| 708 |
+
updateProgress(progressBar.querySelector('.progress'), percentComplete);
|
| 709 |
+
}
|
| 710 |
+
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 711 |
|
| 712 |
+
xhr.onload = function() {
|
| 713 |
+
loadingSpinner.style.display = 'none';
|
| 714 |
+
uploadBtn.disabled = false;
|
| 715 |
+
|
| 716 |
+
if (xhr.status === 200) {
|
| 717 |
+
const response = JSON.parse(xhr.responseText);
|
| 718 |
+
if (response.url) {
|
| 719 |
+
addResultLink(response.url, file.name, response.originalExtension);
|
| 720 |
+
saveToHistory(file.name, response.url, response.originalExtension);
|
| 721 |
+
resetUploadState();
|
| 722 |
+
} else {
|
| 723 |
+
alert('Upload failed: ' + response.error);
|
| 724 |
+
}
|
| 725 |
+
} else {
|
| 726 |
+
try {
|
| 727 |
+
const errorResponse = JSON.parse(xhr.responseText);
|
| 728 |
+
alert(`Error: ${errorResponse.error || 'An unknown error occurred.'}`);
|
| 729 |
+
} catch (e) {
|
| 730 |
+
alert(`An unexpected error occurred. Status: ${xhr.status}`);
|
| 731 |
+
}
|
| 732 |
+
}
|
| 733 |
+
};
|
| 734 |
|
| 735 |
+
xhr.onerror = function() {
|
| 736 |
+
loadingSpinner.style.display = 'none';
|
| 737 |
+
uploadBtn.disabled = false;
|
| 738 |
+
alert('Network error occurred. Please try again.');
|
| 739 |
+
};
|
| 740 |
|
| 741 |
+
xhr.send(formData);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 742 |
}
|
| 743 |
|
| 744 |
function createProgressBar(fileName) {
|
|
|
|
| 757 |
return container;
|
| 758 |
}
|
| 759 |
|
| 760 |
+
function updateProgress(progressBar, percent) {
|
| 761 |
+
progressBar.style.width = percent + '%';
|
|
|
|
|
|
|
|
|
|
| 762 |
}
|
| 763 |
|
| 764 |
function resetUploadState() {
|
|
|
|
| 946 |
|
| 947 |
cookies = await get_cookies()
|
| 948 |
if 'csrftoken' not in cookies or 'sessionid' not in cookies:
|
| 949 |
+
return JSONResponse(content={"error": "Failed to get authentication cookies from Replicate."}, status_code=500)
|
| 950 |
|
| 951 |
+
original_extension = os.path.splitext(file.filename)[1][1:].lower()
|
| 952 |
supported_types = ['mp4', 'png', 'jpg', 'jpeg', 'gif', 'mp3', 'pdf', 'txt']
|
| 953 |
|
| 954 |
+
if original_extension in supported_types:
|
| 955 |
temp_filename = file.filename
|
| 956 |
content_type = file.content_type
|
| 957 |
else:
|
|
|
|
| 960 |
|
| 961 |
upload_result = await initiate_upload(cookies, temp_filename, content_type)
|
| 962 |
if not upload_result or 'upload_url' not in upload_result:
|
| 963 |
+
return JSONResponse(content={"error": "Failed to initiate upload with Replicate. The endpoint may be protected or changed."}, status_code=500)
|
| 964 |
|
| 965 |
+
# Log the real Replicate URL for debugging
|
| 966 |
+
print(f"Real Replicate Serving URL: {upload_result.get('serving_url')}")
|
| 967 |
|
| 968 |
file_content = await file.read()
|
| 969 |
upload_success = await retry_upload(upload_result['upload_url'], file_content, content_type)
|
| 970 |
if not upload_success:
|
| 971 |
+
return JSONResponse(content={"error": "File upload to Replicate's storage failed after multiple attempts."}, status_code=500)
|
| 972 |
|
| 973 |
original_url = upload_result['serving_url']
|
| 974 |
mirrored_url = f"/upload/{original_url.split('/pbxt/')[1]}"
|
| 975 |
|
| 976 |
+
if original_extension not in supported_types:
|
| 977 |
+
# If the original file was unsupported, the mirrored URL from replicate will have .png, remove it.
|
| 978 |
mirrored_url = mirrored_url.replace('.png', '')
|
| 979 |
|
| 980 |
return JSONResponse(content={"url": mirrored_url, "originalExtension": original_extension})
|
| 981 |
|
| 982 |
+
|
| 983 |
@app.get("/upload/{path:path}")
|
| 984 |
async def handle_file_stream(path: str, request: Request):
|
| 985 |
original_url = f'https://replicate.delivery/pbxt/{path}'
|
| 986 |
|
| 987 |
+
# If the path doesn't have a common extension, it's likely an unsupported type we wrapped in .png
|
| 988 |
+
if not any(path.lower().endswith(f".{ext}") for ext in ['png', 'jpg', 'jpeg', 'gif', 'mp4', 'mp3', 'pdf', 'txt']):
|
| 989 |
original_url += '.png'
|
| 990 |
|
| 991 |
range_header = request.headers.get('Range')
|
|
|
|
| 1006 |
if response.status_code == 206:
|
| 1007 |
response_headers['Content-Range'] = response.headers.get('Content-Range')
|
| 1008 |
|
| 1009 |
+
# Guess content type from original extension in path for correct browser handling
|
| 1010 |
original_extension = os.path.splitext(path)[1][1:]
|
| 1011 |
content_type, _ = mimetypes.guess_type(f"file.{original_extension}")
|
| 1012 |
if content_type:
|
|
|
|
| 1018 |
|
| 1019 |
|
| 1020 |
@app.get("/embed")
|
| 1021 |
+
async def embed_video(url: str, thumbnail: str = "https://i.imgur.com/8liH2yB.png"):
|
| 1022 |
+
# Added a default thumbnail
|
| 1023 |
html = f'''
|
| 1024 |
<html>
|
| 1025 |
<head>
|
|
|
|
| 1037 |
<meta property="og:image:type" content="image/png">
|
| 1038 |
<style>
|
| 1039 |
body, html {{ margin: 0; padding: 0; height: 100%; background: #000; }}
|
| 1040 |
+
video {{ width: 100%; height: 100%; object-fit: contain; }}
|
|
|
|
| 1041 |
</style>
|
| 1042 |
</head>
|
| 1043 |
<body>
|
| 1044 |
+
<video controls autoplay>
|
|
|
|
| 1045 |
<source src="{url}" type="video/mp4">
|
| 1046 |
Your browser does not support the video tag.
|
| 1047 |
</video>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1048 |
</body>
|
| 1049 |
</html>
|
| 1050 |
'''
|
| 1051 |
return HTMLResponse(content=html)
|
| 1052 |
|
| 1053 |
+
|
| 1054 |
async def get_cookies() -> Dict[str, str]:
|
| 1055 |
try:
|
| 1056 |
+
response = requests.get('https://replicate.com/explore', headers={
|
| 1057 |
'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'
|
| 1058 |
})
|
| 1059 |
response.raise_for_status()
|
| 1060 |
return dict(response.cookies)
|
| 1061 |
except requests.exceptions.RequestException as e:
|
| 1062 |
+
print(f'Error fetching cookies from Replicate: {e}')
|
| 1063 |
return {}
|
| 1064 |
|
| 1065 |
+
|
| 1066 |
async def initiate_upload(cookies: Dict[str, str], filename: str, content_type: str) -> Dict:
|
| 1067 |
url = f'https://replicate.com/api/upload/{filename}?content_type={content_type}'
|
| 1068 |
+
headers = {
|
| 1069 |
+
'X-CSRFToken': cookies.get('csrftoken'),
|
| 1070 |
+
'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',
|
| 1071 |
+
'Referer': 'https://replicate.com/explore',
|
| 1072 |
+
'Origin': 'https://replicate.com',
|
| 1073 |
+
'Accept': '*/*',
|
| 1074 |
+
'Accept-Language': 'en-US,en;q=0.5',
|
| 1075 |
+
'Accept-Encoding': 'gzip, deflate, br', # More standard encoding
|
| 1076 |
+
'Sec-Fetch-Dest': 'empty',
|
| 1077 |
+
'Sec-Fetch-Mode': 'cors',
|
| 1078 |
+
'Sec-Fetch-Site': 'same-origin',
|
| 1079 |
+
}
|
| 1080 |
try:
|
| 1081 |
+
response = requests.post(url, cookies=cookies, headers=headers)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1082 |
response.raise_for_status()
|
| 1083 |
return response.json()
|
| 1084 |
+
except requests.exceptions.HTTPError as http_err:
|
| 1085 |
+
print(f'HTTP error during upload initiation: {http_err}')
|
| 1086 |
+
print(f'Response status: {response.status_code}')
|
| 1087 |
+
print(f'Response text: {response.text}')
|
| 1088 |
+
return {}
|
| 1089 |
+
except json.JSONDecodeError:
|
| 1090 |
+
print('Failed to decode JSON from Replicate API.')
|
| 1091 |
+
print(f'Response status: {response.status_code}')
|
| 1092 |
+
print(f'Response text: {response.text}') # This is the crucial log
|
| 1093 |
+
return {}
|
| 1094 |
+
except Exception as e:
|
| 1095 |
+
print(f'An unexpected error occurred during upload initiation: {e}')
|
| 1096 |
return {}
|
| 1097 |
|
| 1098 |
+
|
| 1099 |
async def upload_file_to_gcs(upload_url: str, file_content: bytes, content_type: str) -> bool:
|
| 1100 |
try:
|
| 1101 |
response = requests.put(upload_url, data=file_content, headers={'Content-Type': content_type})
|
| 1102 |
response.raise_for_status()
|
| 1103 |
return response.status_code == 200
|
| 1104 |
except requests.exceptions.RequestException as e:
|
| 1105 |
+
print(f'Error during final file upload to GCS: {e}')
|
| 1106 |
return False
|
| 1107 |
|
| 1108 |
+
|
| 1109 |
+
async def retry_upload(upload_url: str, file_content: bytes, content_type: str, max_retries: int = 3, delay: int = 1) -> bool:
|
| 1110 |
for attempt in range(max_retries):
|
| 1111 |
+
success = await upload_file_to_gcs(upload_url, file_content, content_type)
|
| 1112 |
+
if success:
|
| 1113 |
+
return True
|
| 1114 |
+
print(f"Upload attempt {attempt + 1} failed. Retrying in {delay} seconds...")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1115 |
await asyncio.sleep(delay)
|
| 1116 |
+
delay *= 2
|
|
|
|
| 1117 |
return False
|