bharathkumarms commited on
Commit
225070f
·
verified ·
1 Parent(s): 487919e

Upload 9 files

Browse files
Files changed (9) hide show
  1. Dockerfile +25 -0
  2. README.md +61 -0
  3. app.py +6 -0
  4. main.py +657 -0
  5. packages.txt +2 -0
  6. requirements.txt +4 -0
  7. spaces.yml +6 -0
  8. start.bat +6 -0
  9. startup.sh +8 -0
Dockerfile ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Install system dependencies
6
+ RUN apt-get update && apt-get install -y \
7
+ gcc \
8
+ && rm -rf /var/lib/apt/lists/*
9
+
10
+ # Copy requirements and install Python dependencies
11
+ COPY requirements.txt .
12
+ RUN pip install --no-cache-dir -r requirements.txt
13
+
14
+ # Copy the application
15
+ COPY main.py .
16
+ COPY app.py .
17
+
18
+ # Create uploads directory
19
+ RUN mkdir -p uploads
20
+
21
+ # Expose port
22
+ EXPOSE 7860
23
+
24
+ # Run the application
25
+ CMD ["python", "app.py"]
README.md ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Image Metadata Extractor
3
+ emoji: 📸
4
+ colorFrom: blue
5
+ colorTo: purple
6
+ sdk: docker
7
+ app_file: main.py
8
+ pinned: false
9
+ ---
10
+
11
+ # Image Metadata Extractor
12
+
13
+ A simple and elegant web application for extracting metadata from images, including creation timestamps, EXIF data, and file information.
14
+
15
+ ## Features
16
+
17
+ - 📸 Extract EXIF metadata from images
18
+ - ⏱️ Find creation timestamps from EXIF data or file system
19
+ - 🖼️ Display image properties (format, size, color mode)
20
+ - 🎨 Beautiful, responsive web interface
21
+ - 📱 Mobile-friendly design
22
+ - 🐳 Docker support for easy deployment
23
+
24
+ ## How to Use
25
+
26
+ 1. Run the application (instructions below)
27
+ 2. Open your browser to the provided URL
28
+ 3. Upload an image using the file selector or drag & drop
29
+ 4. View the extracted metadata including creation timestamps
30
+
31
+ ## Running Locally
32
+
33
+ ### With Docker (Recommended)
34
+
35
+ ```bash
36
+ docker build -t image-metadata-extractor .
37
+ docker run -p 8000:8000 image-metadata-extractor
38
+ ```
39
+
40
+ ### Without Docker
41
+
42
+ 1. Install the required packages:
43
+ ```bash
44
+ pip install -r requirements.txt
45
+ ```
46
+
47
+ 2. Run the application:
48
+ ```bash
49
+ python main.py
50
+ ```
51
+
52
+ ## API Endpoints
53
+
54
+ - `GET /` - Serve the web interface
55
+ - `POST /extract-metadata` - Extract metadata from an uploaded image
56
+
57
+ ## License
58
+
59
+ MIT
60
+
61
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ # Hugging Face Spaces entry point
2
+ from main import app
3
+
4
+ if __name__ == "__main__":
5
+ import uvicorn
6
+ uvicorn.run(app, host="0.0.0.0", port=7860)
main.py ADDED
@@ -0,0 +1,657 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, File, UploadFile, HTTPException
2
+ from fastapi.responses import HTMLResponse
3
+ from PIL import Image
4
+ from PIL.ExifTags import TAGS
5
+ from datetime import datetime
6
+ import os
7
+
8
+ app = FastAPI(title="Image Metadata Extractor", description="Upload an image to extract its metadata including creation timestamp")
9
+
10
+ # Create uploads directory if it doesn't exist
11
+ UPLOAD_DIR = "uploads"
12
+ if not os.path.exists(UPLOAD_DIR):
13
+ os.makedirs(UPLOAD_DIR)
14
+
15
+ def get_image_metadata(image_path):
16
+ """Extract metadata from an image file"""
17
+ result = {
18
+ "filename": os.path.basename(image_path),
19
+ "format": None,
20
+ "size": None,
21
+ "mode": None,
22
+ "exif_data": {},
23
+ "creation_date": None,
24
+ "file_system_creation_time": None,
25
+ "error": None
26
+ }
27
+
28
+ try:
29
+ # Open the image
30
+ with Image.open(image_path) as img:
31
+ result["format"] = img.format
32
+ result["size"] = img.size
33
+ result["mode"] = img.mode
34
+
35
+ # Get EXIF data
36
+ exif_data = img._getexif()
37
+ if exif_data is not None:
38
+ exif_dict = {}
39
+ for tag_id, value in exif_data.items():
40
+ tag = TAGS.get(tag_id, tag_id)
41
+ exif_dict[tag] = str(value)
42
+ result["exif_data"] = exif_dict
43
+
44
+ # Try to extract creation date from common EXIF tags
45
+ date_tags = ['DateTime', 'DateTimeOriginal', 'DateTimeDigitized']
46
+ for tag in date_tags:
47
+ # Find the tag ID for the current tag
48
+ tag_id = None
49
+ for tid, tname in TAGS.items():
50
+ if tname == tag:
51
+ tag_id = tid
52
+ break
53
+
54
+ if tag_id and tag_id in exif_data:
55
+ raw_date = exif_data[tag_id]
56
+ try:
57
+ # Handle different date formats
58
+ if isinstance(raw_date, bytes):
59
+ raw_date = raw_date.decode('utf-8')
60
+
61
+ # Try different date formats
62
+ formats_to_try = [
63
+ '%Y:%m:%d %H:%M:%S',
64
+ '%Y-%m-%d %H:%M:%S',
65
+ '%Y/%m/%d %H:%M:%S'
66
+ ]
67
+
68
+ creation_date = None
69
+ for fmt in formats_to_try:
70
+ try:
71
+ creation_date = datetime.strptime(str(raw_date), fmt)
72
+ break
73
+ except ValueError:
74
+ continue
75
+
76
+ if creation_date:
77
+ result["creation_date"] = {
78
+ "source": tag,
79
+ "date": creation_date.isoformat()
80
+ }
81
+ break
82
+ except Exception:
83
+ continue
84
+
85
+ # Fallback: Use file system creation time
86
+ stat = os.stat(image_path)
87
+ creation_time = stat.st_ctime
88
+ result["file_system_creation_time"] = datetime.fromtimestamp(creation_time).isoformat()
89
+
90
+ except Exception as e:
91
+ result["error"] = str(e)
92
+
93
+ return result
94
+
95
+ @app.get("/", response_class=HTMLResponse)
96
+ async def read_root():
97
+ """Serve the HTML frontend"""
98
+ return """
99
+ <!DOCTYPE html>
100
+ <html>
101
+ <head>
102
+ <title>Image Metadata Extractor</title>
103
+ <style>
104
+ :root {
105
+ --primary-color: #4361ee;
106
+ --secondary-color: #3f37c9;
107
+ --accent-color: #4895ef;
108
+ --success-color: #4cc9f0;
109
+ --light-color: #f8f9fa;
110
+ --dark-color: #212529;
111
+ --danger-color: #f72585;
112
+ --warning-color: #f8961e;
113
+ --info-color: #56cfe1;
114
+ }
115
+
116
+ * {
117
+ margin: 0;
118
+ padding: 0;
119
+ box-sizing: border-box;
120
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
121
+ }
122
+
123
+ body {
124
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
125
+ min-height: 100vh;
126
+ padding: 20px;
127
+ }
128
+
129
+ .container {
130
+ max-width: 1000px;
131
+ margin: 0 auto;
132
+ }
133
+
134
+ header {
135
+ text-align: center;
136
+ padding: 30px 0;
137
+ color: white;
138
+ text-shadow: 0 2px 4px rgba(0,0,0,0.3);
139
+ }
140
+
141
+ h1 {
142
+ font-size: 2.5rem;
143
+ margin-bottom: 10px;
144
+ }
145
+
146
+ .subtitle {
147
+ font-size: 1.2rem;
148
+ opacity: 0.9;
149
+ }
150
+
151
+ .card {
152
+ background: rgba(255, 255, 255, 0.95);
153
+ border-radius: 15px;
154
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
155
+ padding: 30px;
156
+ margin-bottom: 30px;
157
+ backdrop-filter: blur(10px);
158
+ }
159
+
160
+ .upload-area {
161
+ border: 3px dashed var(--accent-color);
162
+ border-radius: 12px;
163
+ padding: 40px 20px;
164
+ text-align: center;
165
+ transition: all 0.3s ease;
166
+ background: rgba(72, 149, 239, 0.05);
167
+ cursor: pointer;
168
+ }
169
+
170
+ .upload-area:hover {
171
+ border-color: var(--primary-color);
172
+ background: rgba(67, 97, 238, 0.1);
173
+ transform: translateY(-2px);
174
+ }
175
+
176
+ .upload-area.active {
177
+ border-color: var(--success-color);
178
+ background: rgba(76, 201, 240, 0.1);
179
+ }
180
+
181
+ .upload-icon {
182
+ font-size: 4rem;
183
+ color: var(--accent-color);
184
+ margin-bottom: 20px;
185
+ }
186
+
187
+ .upload-text {
188
+ font-size: 1.3rem;
189
+ color: var(--dark-color);
190
+ margin-bottom: 15px;
191
+ }
192
+
193
+ .upload-hint {
194
+ color: #6c757d;
195
+ margin-bottom: 20px;
196
+ }
197
+
198
+ .file-input {
199
+ display: none;
200
+ }
201
+
202
+ .upload-btn {
203
+ background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
204
+ color: white;
205
+ border: none;
206
+ padding: 15px 40px;
207
+ font-size: 1.1rem;
208
+ border-radius: 50px;
209
+ cursor: pointer;
210
+ transition: all 0.3s ease;
211
+ box-shadow: 0 4px 15px rgba(67, 97, 238, 0.3);
212
+ }
213
+
214
+ .upload-btn:hover {
215
+ transform: translateY(-2px);
216
+ box-shadow: 0 6px 20px rgba(67, 97, 238, 0.4);
217
+ }
218
+
219
+ .upload-btn:active {
220
+ transform: translateY(0);
221
+ }
222
+
223
+ .result-container {
224
+ display: none;
225
+ }
226
+
227
+ .result-header {
228
+ text-align: center;
229
+ margin-bottom: 25px;
230
+ color: var(--dark-color);
231
+ }
232
+
233
+ .metadata-grid {
234
+ display: grid;
235
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
236
+ gap: 20px;
237
+ margin-bottom: 30px;
238
+ }
239
+
240
+ .metadata-card {
241
+ background: white;
242
+ border-radius: 10px;
243
+ padding: 20px;
244
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
245
+ border-left: 4px solid var(--primary-color);
246
+ transition: transform 0.3s ease;
247
+ }
248
+
249
+ .metadata-card:hover {
250
+ transform: translateY(-5px);
251
+ box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
252
+ }
253
+
254
+ .metadata-card.creation {
255
+ border-left-color: var(--success-color);
256
+ }
257
+
258
+ .metadata-card.exif {
259
+ border-left-color: var(--warning-color);
260
+ }
261
+
262
+ .metadata-card h3 {
263
+ color: var(--primary-color);
264
+ margin-bottom: 15px;
265
+ font-size: 1.3rem;
266
+ }
267
+
268
+ .metadata-card.creation h3 {
269
+ color: var(--success-color);
270
+ }
271
+
272
+ .metadata-card.exif h3 {
273
+ color: var(--warning-color);
274
+ }
275
+
276
+ .metadata-item {
277
+ margin: 12px 0;
278
+ padding: 10px;
279
+ background: #f8f9fa;
280
+ border-radius: 8px;
281
+ border-left: 3px solid var(--accent-color);
282
+ }
283
+
284
+ .metadata-item strong {
285
+ display: block;
286
+ color: var(--dark-color);
287
+ margin-bottom: 5px;
288
+ }
289
+
290
+ .metadata-item span {
291
+ color: #6c757d;
292
+ }
293
+
294
+ .exif-list {
295
+ max-height: 300px;
296
+ overflow-y: auto;
297
+ }
298
+
299
+ .exif-item {
300
+ display: flex;
301
+ justify-content: space-between;
302
+ padding: 8px 0;
303
+ border-bottom: 1px solid #eee;
304
+ }
305
+
306
+ .exif-item:last-child {
307
+ border-bottom: none;
308
+ }
309
+
310
+ .exif-key {
311
+ font-weight: 500;
312
+ color: var(--dark-color);
313
+ }
314
+
315
+ .exif-value {
316
+ color: #6c757d;
317
+ text-align: right;
318
+ max-width: 60%;
319
+ word-break: break-word;
320
+ }
321
+
322
+ .error {
323
+ background: #ffebee;
324
+ border-left-color: var(--danger-color);
325
+ color: var(--danger-color);
326
+ }
327
+
328
+ .error strong {
329
+ color: var(--danger-color);
330
+ }
331
+
332
+ .actions {
333
+ text-align: center;
334
+ margin-top: 20px;
335
+ }
336
+
337
+ .reset-btn {
338
+ background: transparent;
339
+ color: var(--primary-color);
340
+ border: 2px solid var(--primary-color);
341
+ padding: 12px 30px;
342
+ font-size: 1rem;
343
+ border-radius: 50px;
344
+ cursor: pointer;
345
+ transition: all 0.3s ease;
346
+ }
347
+
348
+ .reset-btn:hover {
349
+ background: var(--primary-color);
350
+ color: white;
351
+ }
352
+
353
+ footer {
354
+ text-align: center;
355
+ color: rgba(255, 255, 255, 0.8);
356
+ padding: 20px 0;
357
+ font-size: 0.9rem;
358
+ }
359
+
360
+ @media (max-width: 768px) {
361
+ .metadata-grid {
362
+ grid-template-columns: 1fr;
363
+ }
364
+
365
+ h1 {
366
+ font-size: 2rem;
367
+ }
368
+
369
+ .card {
370
+ padding: 20px;
371
+ }
372
+ }
373
+ </style>
374
+ </head>
375
+ <body>
376
+ <div class="container">
377
+ <header>
378
+ <h1>📸 Image Metadata Extractor</h1>
379
+ <p class="subtitle">Upload any image to extract detailed metadata including creation timestamps</p>
380
+ </header>
381
+
382
+ <main>
383
+ <div class="card">
384
+ <div class="upload-area" id="upload-area">
385
+ <div class="upload-icon">📁</div>
386
+ <h2 class="upload-text">Drag & Drop your image here</h2>
387
+ <p class="upload-hint">or click to browse files</p>
388
+ <input type="file" id="file-input" class="file-input" accept="image/*">
389
+ <button class="upload-btn" id="upload-btn">Select Image</button>
390
+ </div>
391
+ </div>
392
+
393
+ <div class="card result-container" id="result-container">
394
+ <h2 class="result-header">Image Metadata Analysis</h2>
395
+ <div class="metadata-grid" id="metadata-content"></div>
396
+ <div class="actions">
397
+ <button class="reset-btn" id="reset-btn">Analyze Another Image</button>
398
+ </div>
399
+ </div>
400
+ </main>
401
+
402
+ <footer>
403
+ <p>Image Metadata Extractor &copy; 2025 | Extract creation timestamps and EXIF data</p>
404
+ </footer>
405
+ </div>
406
+
407
+ <script>
408
+ const uploadArea = document.getElementById('upload-area');
409
+ const fileInput = document.getElementById('file-input');
410
+ const uploadBtn = document.getElementById('upload-btn');
411
+ const resultContainer = document.getElementById('result-container');
412
+ const metadataContent = document.getElementById('metadata-content');
413
+ const resetBtn = document.getElementById('reset-btn');
414
+
415
+ // Event listeners
416
+ uploadBtn.addEventListener('click', () => fileInput.click());
417
+ fileInput.addEventListener('change', handleFileSelect);
418
+ uploadArea.addEventListener('dragover', handleDragOver);
419
+ uploadArea.addEventListener('dragleave', handleDragLeave);
420
+ uploadArea.addEventListener('drop', handleDrop);
421
+ resetBtn.addEventListener('click', resetForm);
422
+
423
+ function handleDragOver(e) {
424
+ e.preventDefault();
425
+ uploadArea.classList.add('active');
426
+ }
427
+
428
+ function handleDragLeave() {
429
+ uploadArea.classList.remove('active');
430
+ }
431
+
432
+ function handleDrop(e) {
433
+ e.preventDefault();
434
+ uploadArea.classList.remove('active');
435
+
436
+ if (e.dataTransfer.files.length) {
437
+ fileInput.files = e.dataTransfer.files;
438
+ processFile(fileInput.files[0]);
439
+ }
440
+ }
441
+
442
+ function handleFileSelect() {
443
+ if (fileInput.files.length) {
444
+ processFile(fileInput.files[0]);
445
+ }
446
+ }
447
+
448
+ async function processFile(file) {
449
+ if (!file.type.startsWith('image/')) {
450
+ alert('Please select an image file');
451
+ return;
452
+ }
453
+
454
+ const formData = new FormData();
455
+ formData.append('file', file);
456
+
457
+ try {
458
+ // Show loading state
459
+ uploadBtn.textContent = 'Processing...';
460
+ uploadBtn.disabled = true;
461
+
462
+ const response = await fetch('/extract-metadata', {
463
+ method: 'POST',
464
+ body: formData
465
+ });
466
+
467
+ const data = await response.json();
468
+
469
+ if (response.ok) {
470
+ displayMetadata(data);
471
+ } else {
472
+ showError(data.detail || 'Error processing image');
473
+ }
474
+ } catch (error) {
475
+ showError('Error: ' + error.message);
476
+ } finally {
477
+ uploadBtn.textContent = 'Select Image';
478
+ uploadBtn.disabled = false;
479
+ }
480
+ }
481
+
482
+ function displayMetadata(data) {
483
+ // Hide upload area and show results
484
+ uploadArea.style.display = 'none';
485
+ resultContainer.style.display = 'block';
486
+
487
+ // Generate metadata cards
488
+ let html = '';
489
+
490
+ // Basic info card
491
+ html += `
492
+ <div class="metadata-card">
493
+ <h3>📋 Basic Information</h3>
494
+ <div class="metadata-item">
495
+ <strong>Filename</strong>
496
+ <span>${data.filename}</span>
497
+ </div>
498
+ <div class="metadata-item">
499
+ <strong>Format</strong>
500
+ <span>${data.format || 'Unknown'}</span>
501
+ </div>
502
+ <div class="metadata-item">
503
+ <strong>Dimensions</strong>
504
+ <span>${data.size ? `${data.size[0]} × ${data.size[1]} pixels` : 'Unknown'}</span>
505
+ </div>
506
+ <div class="metadata-item">
507
+ <strong>Color Mode</strong>
508
+ <span>${data.mode || 'Unknown'}</span>
509
+ </div>
510
+ </div>
511
+ `;
512
+
513
+ // Creation date card
514
+ html += `
515
+ <div class="metadata-card creation">
516
+ <h3>⏱️ Creation Timestamp</h3>
517
+ `;
518
+
519
+ if (data.creation_date) {
520
+ const date = new Date(data.creation_date.date);
521
+ html += `
522
+ <div class="metadata-item">
523
+ <strong>Source</strong>
524
+ <span>${data.creation_date.source}</span>
525
+ </div>
526
+ <div class="metadata-item">
527
+ <strong>Date & Time</strong>
528
+ <span>${date.toLocaleString()}</span>
529
+ </div>
530
+ `;
531
+ } else if (data.file_system_creation_time) {
532
+ const date = new Date(data.file_system_creation_time);
533
+ html += `
534
+ <div class="metadata-item">
535
+ <strong>Source</strong>
536
+ <span>File System</span>
537
+ </div>
538
+ <div class="metadata-item">
539
+ <strong>Date & Time</strong>
540
+ <span>${date.toLocaleString()}</span>
541
+ </div>
542
+ `;
543
+ } else {
544
+ html += `
545
+ <div class="metadata-item error">
546
+ <strong>No Creation Date Found</strong>
547
+ <span>Neither EXIF data nor file system timestamps contain creation information</span>
548
+ </div>
549
+ `;
550
+ }
551
+
552
+ html += `</div>`;
553
+
554
+ // EXIF data card
555
+ html += `
556
+ <div class="metadata-card exif">
557
+ <h3>🔍 EXIF Metadata</h3>
558
+ `;
559
+
560
+ if (Object.keys(data.exif_data).length > 0) {
561
+ html += `<div class="exif-list">`;
562
+ for (const [key, value] of Object.entries(data.exif_data)) {
563
+ html += `
564
+ <div class="exif-item">
565
+ <span class="exif-key">${key}</span>
566
+ <span class="exif-value">${value}</span>
567
+ </div>
568
+ `;
569
+ }
570
+ html += `</div>`;
571
+ } else {
572
+ html += `
573
+ <div class="metadata-item">
574
+ <strong>No EXIF Data</strong>
575
+ <span>This image doesn't contain EXIF metadata</span>
576
+ </div>
577
+ `;
578
+ }
579
+
580
+ html += `</div>`;
581
+
582
+ // Error card (if any)
583
+ if (data.error) {
584
+ html += `
585
+ <div class="metadata-card">
586
+ <h3>⚠️ Error</h3>
587
+ <div class="metadata-item error">
588
+ <strong>Processing Error</strong>
589
+ <span>${data.error}</span>
590
+ </div>
591
+ </div>
592
+ `;
593
+ }
594
+
595
+ metadataContent.innerHTML = html;
596
+ }
597
+
598
+ function showError(message) {
599
+ // Hide upload area and show results
600
+ uploadArea.style.display = 'none';
601
+ resultContainer.style.display = 'block';
602
+
603
+ metadataContent.innerHTML = `
604
+ <div class="metadata-card">
605
+ <h3>⚠️ Error</h3>
606
+ <div class="metadata-item error">
607
+ <strong>Processing Failed</strong>
608
+ <span>${message}</span>
609
+ </div>
610
+ </div>
611
+ `;
612
+ }
613
+
614
+ function resetForm() {
615
+ // Show upload area and hide results
616
+ uploadArea.style.display = 'block';
617
+ resultContainer.style.display = 'none';
618
+
619
+ // Reset file input
620
+ fileInput.value = '';
621
+ }
622
+ </script>
623
+ </body>
624
+ </html>
625
+ """
626
+
627
+ @app.post("/extract-metadata")
628
+ async def extract_metadata(file: UploadFile = File(...)):
629
+ """Endpoint to extract metadata from uploaded image"""
630
+ if not file.content_type.startswith("image/"):
631
+ raise HTTPException(status_code=400, detail="File must be an image")
632
+
633
+ # Save the uploaded file temporarily
634
+ file_path = os.path.join(UPLOAD_DIR, file.filename)
635
+
636
+ try:
637
+ # Read the file content
638
+ contents = await file.read()
639
+
640
+ # Save to temporary file
641
+ with open(file_path, "wb") as f:
642
+ f.write(contents)
643
+
644
+ # Extract metadata
645
+ metadata = get_image_metadata(file_path)
646
+
647
+ return metadata
648
+ except Exception as e:
649
+ raise HTTPException(status_code=500, detail=f"Error processing image: {str(e)}")
650
+ finally:
651
+ # Clean up temporary file
652
+ if os.path.exists(file_path):
653
+ os.remove(file_path)
654
+
655
+ if __name__ == "__main__":
656
+ import uvicorn
657
+ uvicorn.run(app, host="0.0.0.0", port=8000)
packages.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ python3-dev
2
+ gcc
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ fastapi>=0.68.0
2
+ uvicorn>=0.15.0
3
+ pillow>=8.3.0
4
+ python-multipart>=0.0.5
spaces.yml ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ # Hugging Face Spaces Configuration
2
+ runtime:
3
+ version: "3.9"
4
+ cpu: true
5
+ memory: 8
6
+ storage: 10
start.bat ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ @echo off
2
+ echo Starting Image Metadata Extractor...
3
+ echo Creating uploads directory...
4
+ mkdir uploads 2>nul
5
+ echo Starting server on http://localhost:8000
6
+ uvicorn main:app --host 0.0.0.0 --port 8000
startup.sh ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # Startup script for Image Metadata Extractor
3
+
4
+ # Create uploads directory
5
+ mkdir -p uploads
6
+
7
+ # Start the application
8
+ uvicorn main:app --host 0.0.0.0 --port 8000