bharathkumarms's picture
Update main.py
0f5fe41 verified
from fastapi import FastAPI, File, UploadFile, HTTPException
from fastapi.responses import HTMLResponse
from PIL import Image
from PIL.ExifTags import TAGS
from datetime import datetime
import os
import tempfile
app = FastAPI(title="Image Metadata Extractor", description="Upload an image to extract its metadata including creation timestamp")
def get_image_metadata(image_path):
"""Extract metadata from an image file"""
result = {
"filename": os.path.basename(image_path),
"format": None,
"size": None,
"mode": None,
"exif_data": {},
"creation_date": None,
"file_system_creation_time": None,
"error": None
}
try:
# Open the image
with Image.open(image_path) as img:
result["format"] = img.format
result["size"] = img.size
result["mode"] = img.mode
# Get EXIF data
exif_data = img._getexif()
if exif_data is not None:
exif_dict = {}
for tag_id, value in exif_data.items():
tag = TAGS.get(tag_id, tag_id)
exif_dict[tag] = str(value)
result["exif_data"] = exif_dict
# Try to extract creation date from common EXIF tags
date_tags = ['DateTime', 'DateTimeOriginal', 'DateTimeDigitized']
for tag in date_tags:
# Find the tag ID for the current tag
tag_id = None
for tid, tname in TAGS.items():
if tname == tag:
tag_id = tid
break
if tag_id and tag_id in exif_data:
raw_date = exif_data[tag_id]
try:
# Handle different date formats
if isinstance(raw_date, bytes):
raw_date = raw_date.decode('utf-8')
# Try different date formats
formats_to_try = [
'%Y:%m:%d %H:%M:%S',
'%Y-%m-%d %H:%M:%S',
'%Y/%m/%d %H:%M:%S'
]
creation_date = None
for fmt in formats_to_try:
try:
creation_date = datetime.strptime(str(raw_date), fmt)
break
except ValueError:
continue
if creation_date:
result["creation_date"] = {
"source": tag,
"date": creation_date.isoformat()
}
break
except Exception:
continue
# Fallback: Use file system creation time
stat = os.stat(image_path)
creation_time = stat.st_ctime
result["file_system_creation_time"] = datetime.fromtimestamp(creation_time).isoformat()
except Exception as e:
result["error"] = str(e)
return result
@app.get("/", response_class=HTMLResponse)
async def read_root():
"""Serve the HTML frontend"""
return """
<!DOCTYPE html>
<html>
<head>
<title>Image Metadata Extractor</title>
<style>
:root {
--primary-color: #4361ee;
--secondary-color: #3f37c9;
--accent-color: #4895ef;
--success-color: #4cc9f0;
--light-color: #f8f9fa;
--dark-color: #212529;
--danger-color: #f72585;
--warning-color: #f8961e;
--info-color: #56cfe1;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1000px;
margin: 0 auto;
}
header {
text-align: center;
padding: 30px 0;
color: white;
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
}
h1 {
font-size: 2.5rem;
margin-bottom: 10px;
}
.subtitle {
font-size: 1.2rem;
opacity: 0.9;
}
.card {
background: rgba(255, 255, 255, 0.95);
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
padding: 30px;
margin-bottom: 30px;
backdrop-filter: blur(10px);
}
.upload-area {
border: 3px dashed var(--accent-color);
border-radius: 12px;
padding: 40px 20px;
text-align: center;
transition: all 0.3s ease;
background: rgba(72, 149, 239, 0.05);
cursor: pointer;
}
.upload-area:hover {
border-color: var(--primary-color);
background: rgba(67, 97, 238, 0.1);
transform: translateY(-2px);
}
.upload-area.active {
border-color: var(--success-color);
background: rgba(76, 201, 240, 0.1);
}
.upload-icon {
font-size: 4rem;
color: var(--accent-color);
margin-bottom: 20px;
}
.upload-text {
font-size: 1.3rem;
color: var(--dark-color);
margin-bottom: 15px;
}
.upload-hint {
color: #6c757d;
margin-bottom: 20px;
}
.file-input {
display: none;
}
.upload-btn {
background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
color: white;
border: none;
padding: 15px 40px;
font-size: 1.1rem;
border-radius: 50px;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(67, 97, 238, 0.3);
}
.upload-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(67, 97, 238, 0.4);
}
.upload-btn:active {
transform: translateY(0);
}
.result-container {
display: none;
}
.result-header {
text-align: center;
margin-bottom: 25px;
color: var(--dark-color);
}
.metadata-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.metadata-card {
background: white;
border-radius: 10px;
padding: 20px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
border-left: 4px solid var(--primary-color);
transition: transform 0.3s ease;
}
.metadata-card:hover {
transform: translateY(-5px);
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
}
.metadata-card.creation {
border-left-color: var(--success-color);
}
.metadata-card.exif {
border-left-color: var(--warning-color);
}
.metadata-card h3 {
color: var(--primary-color);
margin-bottom: 15px;
font-size: 1.3rem;
}
.metadata-card.creation h3 {
color: var(--success-color);
}
.metadata-card.exif h3 {
color: var(--warning-color);
}
.metadata-item {
margin: 12px 0;
padding: 10px;
background: #f8f9fa;
border-radius: 8px;
border-left: 3px solid var(--accent-color);
}
.metadata-item strong {
display: block;
color: var(--dark-color);
margin-bottom: 5px;
}
.metadata-item span {
color: #6c757d;
}
.exif-list {
max-height: 300px;
overflow-y: auto;
}
.exif-item {
display: flex;
justify-content: space-between;
padding: 8px 0;
border-bottom: 1px solid #eee;
}
.exif-item:last-child {
border-bottom: none;
}
.exif-key {
font-weight: 500;
color: var(--dark-color);
}
.exif-value {
color: #6c757d;
text-align: right;
max-width: 60%;
word-break: break-word;
}
.error {
background: #ffebee;
border-left-color: var(--danger-color);
color: var(--danger-color);
}
.error strong {
color: var(--danger-color);
}
.actions {
text-align: center;
margin-top: 20px;
}
.reset-btn {
background: transparent;
color: var(--primary-color);
border: 2px solid var(--primary-color);
padding: 12px 30px;
font-size: 1rem;
border-radius: 50px;
cursor: pointer;
transition: all 0.3s ease;
}
.reset-btn:hover {
background: var(--primary-color);
color: white;
}
footer {
text-align: center;
color: rgba(255, 255, 255, 0.8);
padding: 20px 0;
font-size: 0.9rem;
}
@media (max-width: 768px) {
.metadata-grid {
grid-template-columns: 1fr;
}
h1 {
font-size: 2rem;
}
.card {
padding: 20px;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>📸 Image Metadata Extractor</h1>
<p class="subtitle">Upload any image to extract detailed metadata including creation timestamps</p>
</header>
<main>
<div class="card">
<div class="upload-area" id="upload-area">
<div class="upload-icon">📁</div>
<h2 class="upload-text">Drag & Drop your image here</h2>
<p class="upload-hint">or click to browse files</p>
<input type="file" id="file-input" class="file-input" accept="image/*">
<button class="upload-btn" id="upload-btn">Select Image</button>
</div>
</div>
<div class="card result-container" id="result-container">
<h2 class="result-header">Image Metadata Analysis</h2>
<div class="metadata-grid" id="metadata-content"></div>
<div class="actions">
<button class="reset-btn" id="reset-btn">Analyze Another Image</button>
</div>
</div>
</main>
<footer>
<p>Image Metadata Extractor &copy; 2025 | Extract creation timestamps and EXIF data</p>
</footer>
</div>
<script>
const uploadArea = document.getElementById('upload-area');
const fileInput = document.getElementById('file-input');
const uploadBtn = document.getElementById('upload-btn');
const resultContainer = document.getElementById('result-container');
const metadataContent = document.getElementById('metadata-content');
const resetBtn = document.getElementById('reset-btn');
// Event listeners
uploadBtn.addEventListener('click', () => fileInput.click());
fileInput.addEventListener('change', handleFileSelect);
uploadArea.addEventListener('dragover', handleDragOver);
uploadArea.addEventListener('dragleave', handleDragLeave);
uploadArea.addEventListener('drop', handleDrop);
resetBtn.addEventListener('click', resetForm);
function handleDragOver(e) {
e.preventDefault();
uploadArea.classList.add('active');
}
function handleDragLeave() {
uploadArea.classList.remove('active');
}
function handleDrop(e) {
e.preventDefault();
uploadArea.classList.remove('active');
if (e.dataTransfer.files.length) {
fileInput.files = e.dataTransfer.files;
processFile(fileInput.files[0]);
}
}
function handleFileSelect() {
if (fileInput.files.length) {
processFile(fileInput.files[0]);
}
}
async function processFile(file) {
if (!file.type.startsWith('image/')) {
alert('Please select an image file');
return;
}
const formData = new FormData();
formData.append('file', file);
try {
// Show loading state
uploadBtn.textContent = 'Processing...';
uploadBtn.disabled = true;
const response = await fetch('/extract-metadata', {
method: 'POST',
body: formData
});
const data = await response.json();
if (response.ok) {
displayMetadata(data);
} else {
showError(data.detail || 'Error processing image');
}
} catch (error) {
showError('Error: ' + error.message);
} finally {
uploadBtn.textContent = 'Select Image';
uploadBtn.disabled = false;
}
}
function displayMetadata(data) {
// Hide upload area and show results
uploadArea.style.display = 'none';
resultContainer.style.display = 'block';
// Generate metadata cards
let html = '';
// Basic info card
html += `
<div class="metadata-card">
<h3>📋 Basic Information</h3>
<div class="metadata-item">
<strong>Filename</strong>
<span>${data.filename}</span>
</div>
<div class="metadata-item">
<strong>Format</strong>
<span>${data.format || 'Unknown'}</span>
</div>
<div class="metadata-item">
<strong>Dimensions</strong>
<span>${data.size ? `${data.size[0]} × ${data.size[1]} pixels` : 'Unknown'}</span>
</div>
<div class="metadata-item">
<strong>Color Mode</strong>
<span>${data.mode || 'Unknown'}</span>
</div>
</div>
`;
// Creation date card
html += `
<div class="metadata-card creation">
<h3>⏱️ Creation Timestamp</h3>
`;
if (data.creation_date) {
const date = new Date(data.creation_date.date);
html += `
<div class="metadata-item">
<strong>Source</strong>
<span>${data.creation_date.source}</span>
</div>
<div class="metadata-item">
<strong>Date & Time</strong>
<span>${date.toLocaleString()}</span>
</div>
`;
} else if (data.file_system_creation_time) {
const date = new Date(data.file_system_creation_time);
html += `
<div class="metadata-item">
<strong>Source</strong>
<span>File System</span>
</div>
<div class="metadata-item">
<strong>Date & Time</strong>
<span>${date.toLocaleString()}</span>
</div>
`;
} else {
html += `
<div class="metadata-item error">
<strong>No Creation Date Found</strong>
<span>Neither EXIF data nor file system timestamps contain creation information</span>
</div>
`;
}
html += `</div>`;
// EXIF data card
html += `
<div class="metadata-card exif">
<h3>🔍 EXIF Metadata</h3>
`;
if (Object.keys(data.exif_data).length > 0) {
html += `<div class="exif-list">`;
for (const [key, value] of Object.entries(data.exif_data)) {
html += `
<div class="exif-item">
<span class="exif-key">${key}</span>
<span class="exif-value">${value}</span>
</div>
`;
}
html += `</div>`;
} else {
html += `
<div class="metadata-item">
<strong>No EXIF Data</strong>
<span>This image doesn't contain EXIF metadata</span>
</div>
`;
}
html += `</div>`;
// Error card (if any)
if (data.error) {
html += `
<div class="metadata-card">
<h3>⚠️ Error</h3>
<div class="metadata-item error">
<strong>Processing Error</strong>
<span>${data.error}</span>
</div>
</div>
`;
}
metadataContent.innerHTML = html;
}
function showError(message) {
// Hide upload area and show results
uploadArea.style.display = 'none';
resultContainer.style.display = 'block';
metadataContent.innerHTML = `
<div class="metadata-card">
<h3>⚠️ Error</h3>
<div class="metadata-item error">
<strong>Processing Failed</strong>
<span>${message}</span>
</div>
</div>
`;
}
function resetForm() {
// Show upload area and hide results
uploadArea.style.display = 'block';
resultContainer.style.display = 'none';
// Reset file input
fileInput.value = '';
}
</script>
</body>
</html>
"""
@app.post("/extract-metadata")
async def extract_metadata(file: UploadFile = File(...)):
"""Endpoint to extract metadata from uploaded image"""
if not file.content_type.startswith("image/"):
raise HTTPException(status_code=400, detail="File must be an image")
# Initialize tmp_file_path to None
tmp_file_path = None
try:
# Read the file content
contents = await file.read()
# Create a temporary file to store the uploaded image
with tempfile.NamedTemporaryFile(delete=False) as tmp_file:
tmp_file.write(contents)
tmp_file_path = tmp_file.name
# Extract metadata
metadata = get_image_metadata(tmp_file_path)
return metadata
except Exception as e:
raise HTTPException(status_code=500, detail=f"Error processing image: {str(e)}")
finally:
# Clean up temporary file
if tmp_file_path and os.path.exists(tmp_file_path):
os.unlink(tmp_file_path)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)