Spaces:
Paused
Paused
Update app.py
Browse files
app.py
CHANGED
|
@@ -8,7 +8,7 @@ import numpy as np
|
|
| 8 |
import torch
|
| 9 |
|
| 10 |
from fastapi import FastAPI, UploadFile, File
|
| 11 |
-
from fastapi.responses import HTMLResponse, StreamingResponse
|
| 12 |
|
| 13 |
from huggingface_hub import hf_hub_download
|
| 14 |
from torchvision.transforms.functional import normalize
|
|
@@ -370,6 +370,23 @@ HTML_PAGE = """
|
|
| 370 |
transform: none;
|
| 371 |
}
|
| 372 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 373 |
.btn-secondary {
|
| 374 |
background: #f3f4f6;
|
| 375 |
color: #374151;
|
|
@@ -520,6 +537,10 @@ HTML_PAGE = """
|
|
| 520 |
font-weight: 600;
|
| 521 |
}
|
| 522 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 523 |
.image-container {
|
| 524 |
width: 100%;
|
| 525 |
aspect-ratio: 1;
|
|
@@ -642,6 +663,35 @@ HTML_PAGE = """
|
|
| 642 |
background: #3b82f6;
|
| 643 |
}
|
| 644 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 645 |
@media (max-width: 640px) {
|
| 646 |
.card {
|
| 647 |
padding: 20px;
|
|
@@ -669,6 +719,11 @@ HTML_PAGE = """
|
|
| 669 |
right: 20px;
|
| 670 |
max-width: none;
|
| 671 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 672 |
}
|
| 673 |
</style>
|
| 674 |
</head>
|
|
@@ -716,6 +771,17 @@ HTML_PAGE = """
|
|
| 716 |
</div>
|
| 717 |
</div>
|
| 718 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 719 |
<!-- Image Grid -->
|
| 720 |
<div class="image-grid">
|
| 721 |
<div class="image-card">
|
|
@@ -735,7 +801,7 @@ HTML_PAGE = """
|
|
| 735 |
<div class="image-card">
|
| 736 |
<div class="image-label">
|
| 737 |
<span>✨ Enhanced</span>
|
| 738 |
-
<span class="badge">Output</span>
|
| 739 |
</div>
|
| 740 |
<div class="image-container" id="outputContainer">
|
| 741 |
<div class="placeholder">
|
|
@@ -762,6 +828,8 @@ HTML_PAGE = """
|
|
| 762 |
const removeFile = document.getElementById('removeFile');
|
| 763 |
const enhanceBtn = document.getElementById('enhanceBtn');
|
| 764 |
const resetBtn = document.getElementById('resetBtn');
|
|
|
|
|
|
|
| 765 |
const inputPreview = document.getElementById('inputPreview');
|
| 766 |
const outputPreview = document.getElementById('outputPreview');
|
| 767 |
const inputContainer = document.getElementById('inputContainer');
|
|
@@ -775,6 +843,7 @@ HTML_PAGE = """
|
|
| 775 |
let selectedFile = null;
|
| 776 |
let pollingInterval = null;
|
| 777 |
let currentJobId = null;
|
|
|
|
| 778 |
|
| 779 |
// Toast function
|
| 780 |
function showToast(message, type = 'info') {
|
|
@@ -839,6 +908,11 @@ HTML_PAGE = """
|
|
| 839 |
inputContainer.querySelector('.placeholder').style.display = 'block';
|
| 840 |
outputPreview.style.display = 'none';
|
| 841 |
outputContainer.querySelector('.placeholder').style.display = 'block';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 842 |
resetProgress();
|
| 843 |
if (pollingInterval) {
|
| 844 |
clearInterval(pollingInterval);
|
|
@@ -868,6 +942,28 @@ HTML_PAGE = """
|
|
| 868 |
}
|
| 869 |
}
|
| 870 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 871 |
// Enhance function
|
| 872 |
async function enhance() {
|
| 873 |
if (!selectedFile) {
|
|
@@ -878,6 +974,11 @@ HTML_PAGE = """
|
|
| 878 |
// Reset previous results
|
| 879 |
outputPreview.style.display = 'none';
|
| 880 |
outputContainer.querySelector('.placeholder').style.display = 'block';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 881 |
resetProgress();
|
| 882 |
|
| 883 |
// Disable button during processing
|
|
@@ -920,10 +1021,17 @@ HTML_PAGE = """
|
|
| 920 |
const resultResponse = await fetch(`/result/${currentJobId}?t=${Date.now()}`);
|
| 921 |
if (resultResponse.ok) {
|
| 922 |
const blob = await resultResponse.blob();
|
| 923 |
-
|
| 924 |
-
|
|
|
|
|
|
|
|
|
|
| 925 |
outputPreview.style.display = 'block';
|
| 926 |
outputContainer.querySelector('.placeholder').style.display = 'none';
|
|
|
|
|
|
|
|
|
|
|
|
|
| 927 |
showToast('✨ Enhancement completed!', 'success');
|
| 928 |
}
|
| 929 |
|
|
@@ -960,6 +1068,11 @@ HTML_PAGE = """
|
|
| 960 |
resetProgress();
|
| 961 |
outputPreview.style.display = 'none';
|
| 962 |
outputContainer.querySelector('.placeholder').style.display = 'block';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 963 |
enhanceBtn.disabled = true;
|
| 964 |
enhanceBtn.textContent = '🚀 Enhance Face';
|
| 965 |
showToast('Reset complete', 'info');
|
|
@@ -1004,6 +1117,8 @@ HTML_PAGE = """
|
|
| 1004 |
|
| 1005 |
resetBtn.addEventListener('click', resetAll);
|
| 1006 |
|
|
|
|
|
|
|
| 1007 |
// Keyboard shortcuts
|
| 1008 |
document.addEventListener('keydown', function(e) {
|
| 1009 |
if (e.key === 'Enter' && !enhanceBtn.disabled) {
|
|
@@ -1012,10 +1127,15 @@ HTML_PAGE = """
|
|
| 1012 |
if (e.key === 'Escape') {
|
| 1013 |
resetAll();
|
| 1014 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1015 |
});
|
| 1016 |
|
| 1017 |
// Initial state
|
| 1018 |
console.log('✨ Face Enhancer ready!');
|
|
|
|
| 1019 |
</script>
|
| 1020 |
|
| 1021 |
</body>
|
|
@@ -1086,7 +1206,30 @@ async def result(job_id: str):
|
|
| 1086 |
|
| 1087 |
return StreamingResponse(
|
| 1088 |
BytesIO(results[job_id]),
|
| 1089 |
-
media_type="image/png"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1090 |
)
|
| 1091 |
|
| 1092 |
# ====================================================
|
|
|
|
| 8 |
import torch
|
| 9 |
|
| 10 |
from fastapi import FastAPI, UploadFile, File
|
| 11 |
+
from fastapi.responses import HTMLResponse, StreamingResponse, FileResponse
|
| 12 |
|
| 13 |
from huggingface_hub import hf_hub_download
|
| 14 |
from torchvision.transforms.functional import normalize
|
|
|
|
| 370 |
transform: none;
|
| 371 |
}
|
| 372 |
|
| 373 |
+
.btn-success {
|
| 374 |
+
background: #10b981;
|
| 375 |
+
color: white;
|
| 376 |
+
box-shadow: 0 4px 15px rgba(16, 185, 129, 0.4);
|
| 377 |
+
}
|
| 378 |
+
|
| 379 |
+
.btn-success:hover:not(:disabled) {
|
| 380 |
+
transform: translateY(-2px);
|
| 381 |
+
box-shadow: 0 6px 20px rgba(16, 185, 129, 0.5);
|
| 382 |
+
}
|
| 383 |
+
|
| 384 |
+
.btn-success:disabled {
|
| 385 |
+
opacity: 0.5;
|
| 386 |
+
cursor: not-allowed;
|
| 387 |
+
transform: none;
|
| 388 |
+
}
|
| 389 |
+
|
| 390 |
.btn-secondary {
|
| 391 |
background: #f3f4f6;
|
| 392 |
color: #374151;
|
|
|
|
| 537 |
font-weight: 600;
|
| 538 |
}
|
| 539 |
|
| 540 |
+
.image-label .badge-success {
|
| 541 |
+
background: #10b981;
|
| 542 |
+
}
|
| 543 |
+
|
| 544 |
.image-container {
|
| 545 |
width: 100%;
|
| 546 |
aspect-ratio: 1;
|
|
|
|
| 663 |
background: #3b82f6;
|
| 664 |
}
|
| 665 |
|
| 666 |
+
.download-section {
|
| 667 |
+
display: none;
|
| 668 |
+
margin-top: 16px;
|
| 669 |
+
padding: 16px;
|
| 670 |
+
background: #f0fdf4;
|
| 671 |
+
border-radius: 12px;
|
| 672 |
+
border: 1px solid #86efac;
|
| 673 |
+
align-items: center;
|
| 674 |
+
justify-content: space-between;
|
| 675 |
+
flex-wrap: wrap;
|
| 676 |
+
gap: 12px;
|
| 677 |
+
}
|
| 678 |
+
|
| 679 |
+
.download-section.active {
|
| 680 |
+
display: flex;
|
| 681 |
+
animation: slideDown 0.3s ease;
|
| 682 |
+
}
|
| 683 |
+
|
| 684 |
+
.download-info {
|
| 685 |
+
display: flex;
|
| 686 |
+
align-items: center;
|
| 687 |
+
gap: 12px;
|
| 688 |
+
color: #065f46;
|
| 689 |
+
}
|
| 690 |
+
|
| 691 |
+
.download-info .icon {
|
| 692 |
+
font-size: 24px;
|
| 693 |
+
}
|
| 694 |
+
|
| 695 |
@media (max-width: 640px) {
|
| 696 |
.card {
|
| 697 |
padding: 20px;
|
|
|
|
| 719 |
right: 20px;
|
| 720 |
max-width: none;
|
| 721 |
}
|
| 722 |
+
|
| 723 |
+
.download-section {
|
| 724 |
+
flex-direction: column;
|
| 725 |
+
align-items: stretch;
|
| 726 |
+
}
|
| 727 |
}
|
| 728 |
</style>
|
| 729 |
</head>
|
|
|
|
| 771 |
</div>
|
| 772 |
</div>
|
| 773 |
|
| 774 |
+
<!-- Download Section -->
|
| 775 |
+
<div class="download-section" id="downloadSection">
|
| 776 |
+
<div class="download-info">
|
| 777 |
+
<span class="icon">✅</span>
|
| 778 |
+
<span>Enhancement complete! Download your image</span>
|
| 779 |
+
</div>
|
| 780 |
+
<button class="btn btn-success" id="downloadBtn">
|
| 781 |
+
⬇️ Download PNG
|
| 782 |
+
</button>
|
| 783 |
+
</div>
|
| 784 |
+
|
| 785 |
<!-- Image Grid -->
|
| 786 |
<div class="image-grid">
|
| 787 |
<div class="image-card">
|
|
|
|
| 801 |
<div class="image-card">
|
| 802 |
<div class="image-label">
|
| 803 |
<span>✨ Enhanced</span>
|
| 804 |
+
<span class="badge badge-success">Output</span>
|
| 805 |
</div>
|
| 806 |
<div class="image-container" id="outputContainer">
|
| 807 |
<div class="placeholder">
|
|
|
|
| 828 |
const removeFile = document.getElementById('removeFile');
|
| 829 |
const enhanceBtn = document.getElementById('enhanceBtn');
|
| 830 |
const resetBtn = document.getElementById('resetBtn');
|
| 831 |
+
const downloadBtn = document.getElementById('downloadBtn');
|
| 832 |
+
const downloadSection = document.getElementById('downloadSection');
|
| 833 |
const inputPreview = document.getElementById('inputPreview');
|
| 834 |
const outputPreview = document.getElementById('outputPreview');
|
| 835 |
const inputContainer = document.getElementById('inputContainer');
|
|
|
|
| 843 |
let selectedFile = null;
|
| 844 |
let pollingInterval = null;
|
| 845 |
let currentJobId = null;
|
| 846 |
+
let currentResultUrl = null;
|
| 847 |
|
| 848 |
// Toast function
|
| 849 |
function showToast(message, type = 'info') {
|
|
|
|
| 908 |
inputContainer.querySelector('.placeholder').style.display = 'block';
|
| 909 |
outputPreview.style.display = 'none';
|
| 910 |
outputContainer.querySelector('.placeholder').style.display = 'block';
|
| 911 |
+
downloadSection.classList.remove('active');
|
| 912 |
+
if (currentResultUrl) {
|
| 913 |
+
URL.revokeObjectURL(currentResultUrl);
|
| 914 |
+
currentResultUrl = null;
|
| 915 |
+
}
|
| 916 |
resetProgress();
|
| 917 |
if (pollingInterval) {
|
| 918 |
clearInterval(pollingInterval);
|
|
|
|
| 942 |
}
|
| 943 |
}
|
| 944 |
|
| 945 |
+
// Download function
|
| 946 |
+
function downloadImage() {
|
| 947 |
+
if (!currentResultUrl) {
|
| 948 |
+
showToast('No image to download', 'error');
|
| 949 |
+
return;
|
| 950 |
+
}
|
| 951 |
+
|
| 952 |
+
// Create a temporary link element
|
| 953 |
+
const link = document.createElement('a');
|
| 954 |
+
link.href = currentResultUrl;
|
| 955 |
+
|
| 956 |
+
// Generate filename from original file
|
| 957 |
+
let baseName = selectedFile ? selectedFile.name.replace(/\.[^/.]+$/, '') : 'enhanced';
|
| 958 |
+
link.download = `${baseName}_enhanced.png`;
|
| 959 |
+
|
| 960 |
+
document.body.appendChild(link);
|
| 961 |
+
link.click();
|
| 962 |
+
document.body.removeChild(link);
|
| 963 |
+
|
| 964 |
+
showToast('📥 Download started!', 'success');
|
| 965 |
+
}
|
| 966 |
+
|
| 967 |
// Enhance function
|
| 968 |
async function enhance() {
|
| 969 |
if (!selectedFile) {
|
|
|
|
| 974 |
// Reset previous results
|
| 975 |
outputPreview.style.display = 'none';
|
| 976 |
outputContainer.querySelector('.placeholder').style.display = 'block';
|
| 977 |
+
downloadSection.classList.remove('active');
|
| 978 |
+
if (currentResultUrl) {
|
| 979 |
+
URL.revokeObjectURL(currentResultUrl);
|
| 980 |
+
currentResultUrl = null;
|
| 981 |
+
}
|
| 982 |
resetProgress();
|
| 983 |
|
| 984 |
// Disable button during processing
|
|
|
|
| 1021 |
const resultResponse = await fetch(`/result/${currentJobId}?t=${Date.now()}`);
|
| 1022 |
if (resultResponse.ok) {
|
| 1023 |
const blob = await resultResponse.blob();
|
| 1024 |
+
if (currentResultUrl) {
|
| 1025 |
+
URL.revokeObjectURL(currentResultUrl);
|
| 1026 |
+
}
|
| 1027 |
+
currentResultUrl = URL.createObjectURL(blob);
|
| 1028 |
+
outputPreview.src = currentResultUrl;
|
| 1029 |
outputPreview.style.display = 'block';
|
| 1030 |
outputContainer.querySelector('.placeholder').style.display = 'none';
|
| 1031 |
+
|
| 1032 |
+
// Show download section
|
| 1033 |
+
downloadSection.classList.add('active');
|
| 1034 |
+
|
| 1035 |
showToast('✨ Enhancement completed!', 'success');
|
| 1036 |
}
|
| 1037 |
|
|
|
|
| 1068 |
resetProgress();
|
| 1069 |
outputPreview.style.display = 'none';
|
| 1070 |
outputContainer.querySelector('.placeholder').style.display = 'block';
|
| 1071 |
+
downloadSection.classList.remove('active');
|
| 1072 |
+
if (currentResultUrl) {
|
| 1073 |
+
URL.revokeObjectURL(currentResultUrl);
|
| 1074 |
+
currentResultUrl = null;
|
| 1075 |
+
}
|
| 1076 |
enhanceBtn.disabled = true;
|
| 1077 |
enhanceBtn.textContent = '🚀 Enhance Face';
|
| 1078 |
showToast('Reset complete', 'info');
|
|
|
|
| 1117 |
|
| 1118 |
resetBtn.addEventListener('click', resetAll);
|
| 1119 |
|
| 1120 |
+
downloadBtn.addEventListener('click', downloadImage);
|
| 1121 |
+
|
| 1122 |
// Keyboard shortcuts
|
| 1123 |
document.addEventListener('keydown', function(e) {
|
| 1124 |
if (e.key === 'Enter' && !enhanceBtn.disabled) {
|
|
|
|
| 1127 |
if (e.key === 'Escape') {
|
| 1128 |
resetAll();
|
| 1129 |
}
|
| 1130 |
+
if ((e.ctrlKey || e.metaKey) && e.key === 's' && downloadSection.classList.contains('active')) {
|
| 1131 |
+
e.preventDefault();
|
| 1132 |
+
downloadImage();
|
| 1133 |
+
}
|
| 1134 |
});
|
| 1135 |
|
| 1136 |
// Initial state
|
| 1137 |
console.log('✨ Face Enhancer ready!');
|
| 1138 |
+
console.log('💡 Shortcuts: Enter to enhance, Escape to reset, Ctrl+S to download');
|
| 1139 |
</script>
|
| 1140 |
|
| 1141 |
</body>
|
|
|
|
| 1206 |
|
| 1207 |
return StreamingResponse(
|
| 1208 |
BytesIO(results[job_id]),
|
| 1209 |
+
media_type="image/png",
|
| 1210 |
+
headers={
|
| 1211 |
+
"Content-Disposition": f"attachment; filename=enhanced_{job_id[:8]}.png"
|
| 1212 |
+
}
|
| 1213 |
+
)
|
| 1214 |
+
|
| 1215 |
+
@app.get("/download/{job_id}")
|
| 1216 |
+
async def download_result(job_id: str):
|
| 1217 |
+
"""Direct download endpoint that forces browser to download as PNG"""
|
| 1218 |
+
if job_id not in results:
|
| 1219 |
+
return {
|
| 1220 |
+
"status": "processing",
|
| 1221 |
+
"message": "Result not ready yet"
|
| 1222 |
+
}
|
| 1223 |
+
|
| 1224 |
+
return StreamingResponse(
|
| 1225 |
+
BytesIO(results[job_id]),
|
| 1226 |
+
media_type="image/png",
|
| 1227 |
+
headers={
|
| 1228 |
+
"Content-Disposition": f"attachment; filename=enhanced_{job_id[:8]}.png",
|
| 1229 |
+
"Cache-Control": "no-cache, no-store, must-revalidate",
|
| 1230 |
+
"Pragma": "no-cache",
|
| 1231 |
+
"Expires": "0"
|
| 1232 |
+
}
|
| 1233 |
)
|
| 1234 |
|
| 1235 |
# ====================================================
|