Avanish11 commited on
Commit
2ef25f4
·
verified ·
1 Parent(s): 6a2ea73

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +148 -5
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
- const url = URL.createObjectURL(blob);
924
- outputPreview.src = url;
 
 
 
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
  # ====================================================