jebin2 commited on
Commit
18d9ea5
·
1 Parent(s): f204a08
__pycache__/main.cpython-313.pyc ADDED
Binary file (13.1 kB). View file
 
__pycache__/main_base.cpython-313.pyc ADDED
Binary file (5.81 kB). View file
 
__pycache__/process_exception.cpython-313.pyc ADDED
Binary file (668 Bytes). View file
 
image/convert.html CHANGED
@@ -521,6 +521,10 @@
521
  <option value="png">PNG</option>
522
  <option value="webp">WebP</option>
523
  <option value="svg">SVG</option>
 
 
 
 
524
  </select>
525
  </div>
526
  </div>
 
521
  <option value="png">PNG</option>
522
  <option value="webp">WebP</option>
523
  <option value="svg">SVG</option>
524
+ <option value="heic">HEIC</option>
525
+ <option value="tiff">TIFF</option>
526
+ <option value="bmp">BMP</option>
527
+ <option value="gif">GIF</option>
528
  </select>
529
  </div>
530
  </div>
image/converter.py CHANGED
@@ -96,6 +96,11 @@ class Converter(ImageBase):
96
  'optimize': False # Don't optimize palette
97
  }
98
 
 
 
 
 
 
99
  return settings
100
 
101
  def _preserve_metadata(self, original_image: Image.Image, format_name: str) -> Dict:
 
96
  'optimize': False # Don't optimize palette
97
  }
98
 
99
+ elif format_name == 'HEIC':
100
+ settings = {
101
+ 'quality': 100 # Maximum quality for HEIC
102
+ }
103
+
104
  return settings
105
 
106
  def _preserve_metadata(self, original_image: Image.Image, format_name: str) -> Dict:
image/javascript/image.js CHANGED
@@ -2,11 +2,45 @@
2
  let selectedFiles = [];
3
  let completedFiles = [];
4
 
 
 
 
 
5
  // Initialize the application
6
  document.addEventListener('DOMContentLoaded', function () {
 
7
  setupEventListeners();
8
  });
9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  function setupEventListeners() {
11
  const uploadSection = document.getElementById('upload-section');
12
  const fileInput = document.getElementById('file-input');
@@ -75,19 +109,33 @@ function handleFileSelect(e) {
75
  }
76
 
77
  function generateUniqueId(file, length = 12) {
78
- ext = "." + file.name.split(".")[file.name.split(".").length - 1]
79
  const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
80
  return Array.from({ length }, () => chars[Math.floor(Math.random() * chars.length)]).join('') + ext;
81
  }
82
 
 
 
 
83
  function saveFileIdToStorage(id) {
84
- let fileIds = JSON.parse(localStorage.getItem('uploadedFileIds') || '[]');
85
- if (!fileIds.includes(id)) {
86
- fileIds.push(id);
87
- localStorage.setItem('uploadedFileIds', JSON.stringify(fileIds));
 
 
88
  }
89
  }
90
 
 
 
 
 
 
 
 
 
 
91
  function processFiles(files) {
92
  files.forEach((file, index) => {
93
  const id = generateUniqueId(file); // generate unique ID
@@ -132,12 +180,12 @@ function updateUI() {
132
  }
133
 
134
  function renderFileCards() {
135
- const filesGrid = document.getElementById('files-grid');
136
 
137
- filesGrid.innerHTML = selectedFiles.map(file => `
138
  <div class="file-card" id="file-${file.id}">
139
  <button class="remove-button" onclick="removeFile('${file.id}')">×</button>
140
- ${window.location.pathname == '/image/remove_metadata' && file.other_info ? `<button class="remove-button" style="top: 40px; background: #17a2b8;" onclick="showMetadata('${file.id}')">i</button>`: ``}
141
 
142
  <div class="file-preview">
143
  ${file.preview ? `<img src="${file.preview}" alt="${file.name}">` : '<span class="file-icon">🖼️</span>'}
@@ -293,6 +341,7 @@ function formatFileSize(bytes) {
293
  function removeFile(fileId) {
294
  selectedFiles = selectedFiles.filter(file => file.id !== fileId);
295
  completedFiles = completedFiles.filter(file => file.id !== fileId);
 
296
  updateUI();
297
 
298
  if (selectedFiles.length === 0) {
@@ -301,7 +350,8 @@ function removeFile(fileId) {
301
  }
302
 
303
  async function clearAllFiles() {
304
- const fileIds = JSON.parse(localStorage.getItem('uploadedFileIds') || '[]');
 
305
 
306
  if (fileIds.length > 0) {
307
  try {
@@ -323,7 +373,7 @@ async function clearAllFiles() {
323
  updateUI();
324
  document.getElementById('download-button').style.display = 'none';
325
  document.getElementById('progress-section').style.display = 'none';
326
- localStorage.removeItem('uploadedFileIds');
327
  }
328
 
329
  async function upload(file) {
@@ -364,20 +414,20 @@ function getStatusText(status) {
364
  }
365
  }
366
  function showMetadata(fileId) {
367
- const file = selectedFiles.find(f => f.id === fileId);
368
- const contentEl = document.getElementById('metadata-content');
369
- if (file && file.other_info) {
370
- contentEl.textContent = JSON.stringify(file.other_info, null, 2);
371
- document.getElementById('metadata-modal').style.display = 'block';
372
- document.getElementById('modal-backdrop').style.display = 'block';
373
- } else {
374
- contentEl.textContent = 'No metadata removed or unavailable.';
375
- document.getElementById('metadata-modal').style.display = 'block';
376
- document.getElementById('modal-backdrop').style.display = 'block';
377
- }
378
  }
379
 
380
  function closeMetadataModal() {
381
- document.getElementById('metadata-modal').style.display = 'none';
382
- document.getElementById('modal-backdrop').style.display = 'none';
383
  }
 
2
  let selectedFiles = [];
3
  let completedFiles = [];
4
 
5
+ // Constants
6
+ const MAX_AGE_DAYS = 2;
7
+ const MAX_AGE_MS = MAX_AGE_DAYS * 24 * 60 * 60 * 1000;
8
+
9
  // Initialize the application
10
  document.addEventListener('DOMContentLoaded', function () {
11
+ cleanupOrphanedStorageEntries();
12
  setupEventListeners();
13
  });
14
 
15
+ /**
16
+ * Remove localStorage entries older than MAX_AGE_DAYS
17
+ * This prevents orphaned entries from accumulating
18
+ */
19
+ function cleanupOrphanedStorageEntries() {
20
+ try {
21
+ const storageData = JSON.parse(localStorage.getItem('uploadedFileData') || '{}');
22
+ const now = Date.now();
23
+ let cleaned = false;
24
+
25
+ for (const id in storageData) {
26
+ if (now - storageData[id].timestamp > MAX_AGE_MS) {
27
+ delete storageData[id];
28
+ cleaned = true;
29
+ }
30
+ }
31
+
32
+ if (cleaned) {
33
+ localStorage.setItem('uploadedFileData', JSON.stringify(storageData));
34
+ console.log('Cleaned orphaned localStorage entries');
35
+ }
36
+
37
+ // Also clean legacy format if exists
38
+ localStorage.removeItem('uploadedFileIds');
39
+ } catch (e) {
40
+ console.warn('Failed to cleanup localStorage:', e);
41
+ }
42
+ }
43
+
44
  function setupEventListeners() {
45
  const uploadSection = document.getElementById('upload-section');
46
  const fileInput = document.getElementById('file-input');
 
109
  }
110
 
111
  function generateUniqueId(file, length = 12) {
112
+ let ext = "." + file.name.split(".")[file.name.split(".").length - 1]
113
  const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
114
  return Array.from({ length }, () => chars[Math.floor(Math.random() * chars.length)]).join('') + ext;
115
  }
116
 
117
+ /**
118
+ * Save file ID with timestamp to localStorage
119
+ */
120
  function saveFileIdToStorage(id) {
121
+ const storageData = JSON.parse(localStorage.getItem('uploadedFileData') || '{}');
122
+ if (!storageData[id]) {
123
+ storageData[id] = {
124
+ timestamp: Date.now()
125
+ };
126
+ localStorage.setItem('uploadedFileData', JSON.stringify(storageData));
127
  }
128
  }
129
 
130
+ /**
131
+ * Remove file ID from localStorage
132
+ */
133
+ function removeFileIdFromStorage(id) {
134
+ const storageData = JSON.parse(localStorage.getItem('uploadedFileData') || '{}');
135
+ delete storageData[id];
136
+ localStorage.setItem('uploadedFileData', JSON.stringify(storageData));
137
+ }
138
+
139
  function processFiles(files) {
140
  files.forEach((file, index) => {
141
  const id = generateUniqueId(file); // generate unique ID
 
180
  }
181
 
182
  function renderFileCards() {
183
+ const filesGrid = document.getElementById('files-grid');
184
 
185
+ filesGrid.innerHTML = selectedFiles.map(file => `
186
  <div class="file-card" id="file-${file.id}">
187
  <button class="remove-button" onclick="removeFile('${file.id}')">×</button>
188
+ ${window.location.pathname == '/image/remove_metadata' && file.other_info ? `<button class="remove-button" style="top: 40px; background: #17a2b8;" onclick="showMetadata('${file.id}')">i</button>` : ``}
189
 
190
  <div class="file-preview">
191
  ${file.preview ? `<img src="${file.preview}" alt="${file.name}">` : '<span class="file-icon">🖼️</span>'}
 
341
  function removeFile(fileId) {
342
  selectedFiles = selectedFiles.filter(file => file.id !== fileId);
343
  completedFiles = completedFiles.filter(file => file.id !== fileId);
344
+ removeFileIdFromStorage(fileId);
345
  updateUI();
346
 
347
  if (selectedFiles.length === 0) {
 
350
  }
351
 
352
  async function clearAllFiles() {
353
+ const storageData = JSON.parse(localStorage.getItem('uploadedFileData') || '{}');
354
+ const fileIds = Object.keys(storageData);
355
 
356
  if (fileIds.length > 0) {
357
  try {
 
373
  updateUI();
374
  document.getElementById('download-button').style.display = 'none';
375
  document.getElementById('progress-section').style.display = 'none';
376
+ localStorage.removeItem('uploadedFileData');
377
  }
378
 
379
  async function upload(file) {
 
414
  }
415
  }
416
  function showMetadata(fileId) {
417
+ const file = selectedFiles.find(f => f.id === fileId);
418
+ const contentEl = document.getElementById('metadata-content');
419
+ if (file && file.other_info) {
420
+ contentEl.textContent = JSON.stringify(file.other_info, null, 2);
421
+ document.getElementById('metadata-modal').style.display = 'block';
422
+ document.getElementById('modal-backdrop').style.display = 'block';
423
+ } else {
424
+ contentEl.textContent = 'No metadata removed or unavailable.';
425
+ document.getElementById('metadata-modal').style.display = 'block';
426
+ document.getElementById('modal-backdrop').style.display = 'block';
427
+ }
428
  }
429
 
430
  function closeMetadataModal() {
431
+ document.getElementById('metadata-modal').style.display = 'none';
432
+ document.getElementById('modal-backdrop').style.display = 'none';
433
  }
main.py CHANGED
@@ -12,6 +12,20 @@ import mimetypes
12
 
13
  app = FastAPI(title="Tools Collection", description="Collection of utility tools")
14
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  # Mount static/image at /image
16
  app.mount("/image/javascript", StaticFiles(directory="image/javascript"), name="image")
17
 
 
12
 
13
  app = FastAPI(title="Tools Collection", description="Collection of utility tools")
14
 
15
+ # Startup event to cleanup old files
16
+ @app.on_event("startup")
17
+ async def startup_cleanup():
18
+ """Run cleanup of files older than 2 days on server startup"""
19
+ try:
20
+ image_base = ImageBase()
21
+ deleted_count = image_base.cleanup_old_files(max_age_days=2)
22
+ if deleted_count > 0:
23
+ logger_config.info(f"Startup cleanup: Removed {deleted_count} old file(s)")
24
+ else:
25
+ logger_config.info("Startup cleanup: No old files to remove")
26
+ except Exception as e:
27
+ logger_config.warning(f"Startup cleanup failed: {e}")
28
+
29
  # Mount static/image at /image
30
  app.mount("/image/javascript", StaticFiles(directory="image/javascript"), name="image")
31
 
main_base.py CHANGED
@@ -56,4 +56,46 @@ class MainBase:
56
  if name_without_ext in filename:
57
  file_path = os.path.join(self.output_dir, filename)
58
  path_obj = Path(file_path)
59
- path_obj.unlink(missing_ok=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  if name_without_ext in filename:
57
  file_path = os.path.join(self.output_dir, filename)
58
  path_obj = Path(file_path)
59
+ path_obj.unlink(missing_ok=True)
60
+
61
+ def cleanup_old_files(self, max_age_days=2):
62
+ """
63
+ Remove files older than max_age_days from input and output directories.
64
+
65
+ Args:
66
+ max_age_days: Maximum age in days before files are deleted (default: 2)
67
+
68
+ Returns:
69
+ Number of files deleted
70
+ """
71
+ import time
72
+
73
+ deleted_count = 0
74
+ max_age_seconds = max_age_days * 24 * 60 * 60
75
+ current_time = time.time()
76
+
77
+ directories = [self.input_dir, self.output_dir]
78
+
79
+ for directory in directories:
80
+ if not os.path.exists(directory):
81
+ continue
82
+
83
+ for filename in os.listdir(directory):
84
+ file_path = os.path.join(directory, filename)
85
+
86
+ # Skip directories
87
+ if os.path.isdir(file_path):
88
+ continue
89
+
90
+ try:
91
+ file_mtime = os.path.getmtime(file_path)
92
+ file_age = current_time - file_mtime
93
+
94
+ if file_age > max_age_seconds:
95
+ os.unlink(file_path)
96
+ deleted_count += 1
97
+ logger_config.info(f"Cleaned up old file: {filename}")
98
+ except Exception as e:
99
+ logger_config.warning(f"Failed to cleanup {filename}: {e}")
100
+
101
+ return deleted_count