Files
Claude Project Manager a2154e2a30 Update changes
2025-07-10 23:55:34 +02:00

997 Zeilen
39 KiB
JavaScript

// === METADATEN-CRAWLER JAVASCRIPT ===
// DOM Elements
const fileDropZone = document.getElementById('fileDropZone');
const fileInput = document.getElementById('fileInput');
const loading = document.getElementById('loading');
const metadataContainer = document.getElementById('metadataContainer');
const fileInfo = document.getElementById('fileInfo');
const imagePreview = document.getElementById('imagePreview');
const metadataContent = document.getElementById('metadataContent');
// Prevent default drag behaviors
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
fileDropZone.addEventListener(eventName, preventDefaults, false);
document.body.addEventListener(eventName, preventDefaults, false);
});
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
// Highlight drop zone when item is dragged over it
['dragenter', 'dragover'].forEach(eventName => {
fileDropZone.addEventListener(eventName, highlight, false);
});
['dragleave', 'drop'].forEach(eventName => {
fileDropZone.addEventListener(eventName, unhighlight, false);
});
function highlight(e) {
fileDropZone.classList.add('drag-over');
}
function unhighlight(e) {
fileDropZone.classList.remove('drag-over');
}
// Handle dropped files
fileDropZone.addEventListener('drop', handleFileDrop, false);
function handleFileDrop(e) {
const dt = e.dataTransfer;
const files = dt.files;
if (files.length > 0) {
handleFile(files[0]);
}
}
// Handle file selection via click
fileDropZone.addEventListener('click', () => fileInput.click());
fileInput.addEventListener('change', (e) => {
if (e.target.files.length > 0) {
handleFile(e.target.files[0]);
}
});
// Main file handler
async function handleFile(file) {
// Show loading
loading.classList.add('active');
metadataContainer.classList.remove('active');
// Display basic file info
const basicInfo = {
name: file.name,
size: formatFileSize(file.size),
type: file.type,
lastModified: new Date(file.lastModified).toLocaleString('de-DE')
};
// Check if it's an image or video
if (file.type.startsWith('image/')) {
await processImage(file, basicInfo);
} else if (file.type.startsWith('video/')) {
await processVideo(file, basicInfo);
} else {
alert('Bitte wählen Sie eine Bild- oder Videodatei aus.');
loading.classList.remove('active');
return;
}
}
// Process image files
async function processImage(file, basicInfo) {
try {
// Read file as ArrayBuffer for ExifReader
const arrayBuffer = await file.arrayBuffer();
// Extract EXIF data
let exifData = {};
try {
const tags = ExifReader.load(arrayBuffer);
exifData = tags;
} catch (error) {
console.log('Keine EXIF-Daten gefunden oder Fehler beim Lesen:', error);
}
// Create object URL for preview
const objectUrl = URL.createObjectURL(file);
// Get image dimensions
const img = new Image();
img.onload = function() {
basicInfo.width = this.width + ' px';
basicInfo.height = this.height + ' px';
basicInfo.aspect = (this.width / this.height).toFixed(2);
displayImageMetadata(basicInfo, exifData, objectUrl);
URL.revokeObjectURL(objectUrl);
};
img.src = objectUrl;
} catch (error) {
console.error('Fehler beim Verarbeiten der Bilddatei:', error);
alert('Fehler beim Lesen der Metadaten.');
loading.classList.remove('active');
}
}
// Process video files
async function processVideo(file, basicInfo) {
try {
// Create video element to extract basic metadata
const video = document.createElement('video');
const objectUrl = URL.createObjectURL(file);
video.preload = 'metadata';
video.onloadedmetadata = function() {
basicInfo.duration = formatDuration(video.duration);
basicInfo.width = video.videoWidth + ' px';
basicInfo.height = video.videoHeight + ' px';
basicInfo.aspect = (video.videoWidth / video.videoHeight).toFixed(2);
displayVideoMetadata(basicInfo, objectUrl);
URL.revokeObjectURL(objectUrl);
};
video.src = objectUrl;
} catch (error) {
console.error('Fehler beim Verarbeiten der Videodatei:', error);
alert('Fehler beim Lesen der Video-Metadaten.');
loading.classList.remove('active');
}
}
// Display image metadata
function displayImageMetadata(basicInfo, exifData, previewUrl) {
// Store data for report generation
currentFileData = basicInfo;
currentPreviewUrl = previewUrl;
currentMetadata = {};
// Update file info
fileInfo.innerHTML = `
<h3>${basicInfo.name}</h3>
<div class="basic-info">
<span class="label">Größe:</span><span>${basicInfo.size}</span>
<span class="label">Typ:</span><span>${basicInfo.type}</span>
<span class="label">Zuletzt geändert:</span><span>${basicInfo.lastModified}</span>
<span class="label">Auflösung:</span><span>${basicInfo.width} × ${basicInfo.height}</span>
<span class="label">Seitenverhältnis:</span><span>${basicInfo.aspect}:1</span>
</div>
`;
// Show preview
imagePreview.innerHTML = `<img src="${previewUrl}" alt="Vorschau">`;
// Organize metadata into sections
const sections = {
exif: {
title: 'EXIF-Daten',
data: {},
icon: '📷'
},
camera: {
title: 'Kamera-Informationen',
data: {},
icon: '📸'
},
location: {
title: 'Standort-Informationen',
data: {},
icon: '📍'
},
technical: {
title: 'Technische Details',
data: {},
icon: '⚙️'
},
other: {
title: 'Weitere Metadaten',
data: {},
icon: '📋'
}
};
// Process EXIF data and categorize
if (exifData) {
for (const [key, value] of Object.entries(exifData)) {
if (value && value.description !== undefined) {
const desc = value.description;
// Categorize metadata
if (key.includes('Make') || key.includes('Model') || key.includes('Lens')) {
sections.camera.data[key] = desc;
} else if (key.includes('GPS')) {
sections.location.data[key] = desc;
} else if (key.includes('DateTime') || key.includes('Date')) {
sections.exif.data[key] = desc;
} else if (key.includes('ISO') || key.includes('Exposure') || key.includes('Aperture') ||
key.includes('FocalLength') || key.includes('Flash')) {
sections.technical.data[key] = desc;
} else {
sections.other.data[key] = desc;
}
}
}
}
// Build metadata display
let metadataHTML = '';
for (const [sectionKey, section] of Object.entries(sections)) {
if (Object.keys(section.data).length > 0) {
metadataHTML += createMetadataSection(section.title, section.data, section.icon);
// Store for report
currentMetadata[section.title] = section.data;
}
}
if (metadataHTML === '') {
metadataHTML = '<div class="no-metadata">Keine zusätzlichen Metadaten gefunden.</div>';
}
metadataContent.innerHTML = metadataHTML;
// Add click handlers for collapsible sections
addSectionToggleHandlers();
// Hide loading and show metadata
loading.classList.remove('active');
metadataContainer.classList.add('active');
}
// Display video metadata
function displayVideoMetadata(basicInfo, previewUrl) {
// Store data for report generation
currentFileData = basicInfo;
currentPreviewUrl = previewUrl;
currentMetadata = {};
// Update file info
fileInfo.innerHTML = `
<h3>${basicInfo.name}</h3>
<div class="basic-info">
<span class="label">Größe:</span><span>${basicInfo.size}</span>
<span class="label">Typ:</span><span>${basicInfo.type}</span>
<span class="label">Zuletzt geändert:</span><span>${basicInfo.lastModified}</span>
<span class="label">Dauer:</span><span>${basicInfo.duration}</span>
<span class="label">Auflösung:</span><span>${basicInfo.width} × ${basicInfo.height}</span>
<span class="label">Seitenverhältnis:</span><span>${basicInfo.aspect}:1</span>
</div>
`;
// Show video preview (poster frame)
imagePreview.innerHTML = `
<video controls style="max-width: 300px; max-height: 300px; border-radius: 8px;">
<source src="${previewUrl}" type="${basicInfo.type}">
Ihr Browser unterstützt das Video-Tag nicht.
</video>
`;
// For videos, we have limited metadata access from browser
const videoMetadata = {
'Format': basicInfo.type,
'Dauer': basicInfo.duration,
'Auflösung': `${basicInfo.width} × ${basicInfo.height}`,
'Seitenverhältnis': `${basicInfo.aspect}:1`,
'Dateigröße': basicInfo.size
};
let metadataHTML = createMetadataSection('Video-Informationen', videoMetadata, '🎬', 'video');
metadataHTML += '<div class="no-metadata">Erweiterte Video-Metadaten können im Browser nur begrenzt ausgelesen werden.</div>';
// Store for report
currentMetadata['Video-Informationen'] = videoMetadata;
metadataContent.innerHTML = metadataHTML;
// Add click handlers for collapsible sections
addSectionToggleHandlers();
// Hide loading and show metadata
loading.classList.remove('active');
metadataContainer.classList.add('active');
}
// Create metadata section HTML
function createMetadataSection(title, data, icon = '📋', cssClass = '') {
let html = `
<div class="metadata-section ${cssClass}">
<div class="metadata-section-header ${cssClass}">
<span>${icon} ${title}</span>
<span class="toggle-icon">▼</span>
</div>
<div class="metadata-content">
<table class="metadata-table">
`;
for (const [key, value] of Object.entries(data)) {
const formattedKey = formatMetadataKey(key);
const formattedValue = formatMetadataValue(key, value);
const tooltip = getMetadataTooltip(key, formattedKey);
html += `
<tr>
<td>
${formattedKey}
${tooltip ? `<span class="info-icon" title="${tooltip}"><img src="icons/info.svg" alt="Info"></span>` : ''}
</td>
<td>${formattedValue}</td>
</tr>
`;
}
html += `
</table>
</div>
</div>
`;
return html;
}
// Device model mapping for cameras
const deviceModelMapping = {
// Apple iPhone models
'iPhone 15 Pro Max': 'Apple iPhone 15 Pro Max',
'iPhone 15 Pro': 'Apple iPhone 15 Pro',
'iPhone 15 Plus': 'Apple iPhone 15 Plus',
'iPhone 15': 'Apple iPhone 15',
'iPhone 14 Pro Max': 'Apple iPhone 14 Pro Max',
'iPhone 14 Pro': 'Apple iPhone 14 Pro',
'iPhone 14 Plus': 'Apple iPhone 14 Plus',
'iPhone 14': 'Apple iPhone 14',
'iPhone 13 Pro Max': 'Apple iPhone 13 Pro Max',
'iPhone 13 Pro': 'Apple iPhone 13 Pro',
'iPhone 13': 'Apple iPhone 13',
'iPhone 13 mini': 'Apple iPhone 13 mini',
'iPhone 12 Pro Max': 'Apple iPhone 12 Pro Max',
'iPhone 12 Pro': 'Apple iPhone 12 Pro',
'iPhone 12': 'Apple iPhone 12',
'iPhone 12 mini': 'Apple iPhone 12 mini',
'iPhone SE (3rd generation)': 'Apple iPhone SE (2022)',
'iPhone SE (2nd generation)': 'Apple iPhone SE (2020)',
'iPhone 11 Pro Max': 'Apple iPhone 11 Pro Max',
'iPhone 11 Pro': 'Apple iPhone 11 Pro',
'iPhone 11': 'Apple iPhone 11',
'iPhone XS Max': 'Apple iPhone XS Max',
'iPhone XS': 'Apple iPhone XS',
'iPhone XR': 'Apple iPhone XR',
'iPhone X': 'Apple iPhone X',
'iPhone 8 Plus': 'Apple iPhone 8 Plus',
'iPhone 8': 'Apple iPhone 8',
'iPhone 7 Plus': 'Apple iPhone 7 Plus',
'iPhone 7': 'Apple iPhone 7',
'iPhone 6s Plus': 'Apple iPhone 6s Plus',
'iPhone 6s': 'Apple iPhone 6s',
'iPhone 6 Plus': 'Apple iPhone 6 Plus',
'iPhone 6': 'Apple iPhone 6',
// Apple iPad models
'iPad Pro (12.9-inch) (6th generation)': 'Apple iPad Pro 12.9" (2022)',
'iPad Pro (11-inch) (4th generation)': 'Apple iPad Pro 11" (2022)',
'iPad Air (5th generation)': 'Apple iPad Air (2022)',
'iPad (10th generation)': 'Apple iPad (2022)',
'iPad mini (6th generation)': 'Apple iPad mini (2021)',
// Samsung Galaxy models
'SM-S928B': 'Samsung Galaxy S24 Ultra',
'SM-S926B': 'Samsung Galaxy S24+',
'SM-S921B': 'Samsung Galaxy S24',
'SM-S918B': 'Samsung Galaxy S23 Ultra',
'SM-S916B': 'Samsung Galaxy S23+',
'SM-S911B': 'Samsung Galaxy S23',
'SM-S908B': 'Samsung Galaxy S22 Ultra',
'SM-S906B': 'Samsung Galaxy S22+',
'SM-S901B': 'Samsung Galaxy S22',
'SM-G998B': 'Samsung Galaxy S21 Ultra',
'SM-G996B': 'Samsung Galaxy S21+',
'SM-G991B': 'Samsung Galaxy S21',
'SM-G988B': 'Samsung Galaxy S20 Ultra',
'SM-G986B': 'Samsung Galaxy S20+',
'SM-G981B': 'Samsung Galaxy S20',
'SM-A546B': 'Samsung Galaxy A54',
'SM-A346B': 'Samsung Galaxy A34',
'SM-A536B': 'Samsung Galaxy A53',
'SM-A336B': 'Samsung Galaxy A33',
'SM-A526B': 'Samsung Galaxy A52',
'SM-A326B': 'Samsung Galaxy A32',
// Google Pixel models
'Pixel 8 Pro': 'Google Pixel 8 Pro',
'Pixel 8': 'Google Pixel 8',
'Pixel 7a': 'Google Pixel 7a',
'Pixel 7 Pro': 'Google Pixel 7 Pro',
'Pixel 7': 'Google Pixel 7',
'Pixel 6a': 'Google Pixel 6a',
'Pixel 6 Pro': 'Google Pixel 6 Pro',
'Pixel 6': 'Google Pixel 6',
'Pixel 5a': 'Google Pixel 5a',
'Pixel 5': 'Google Pixel 5',
'Pixel 4a': 'Google Pixel 4a',
'Pixel 4 XL': 'Google Pixel 4 XL',
'Pixel 4': 'Google Pixel 4',
// Xiaomi models
'Mi 13 Ultra': 'Xiaomi Mi 13 Ultra',
'Mi 13 Pro': 'Xiaomi Mi 13 Pro',
'Mi 13': 'Xiaomi Mi 13',
'Mi 12 Ultra': 'Xiaomi Mi 12 Ultra',
'Mi 12 Pro': 'Xiaomi Mi 12 Pro',
'Mi 12': 'Xiaomi Mi 12',
'Mi 11 Ultra': 'Xiaomi Mi 11 Ultra',
'Mi 11 Pro': 'Xiaomi Mi 11 Pro',
'Mi 11': 'Xiaomi Mi 11',
'Redmi Note 13 Pro+': 'Xiaomi Redmi Note 13 Pro+',
'Redmi Note 13 Pro': 'Xiaomi Redmi Note 13 Pro',
'Redmi Note 12 Pro+': 'Xiaomi Redmi Note 12 Pro+',
'Redmi Note 12 Pro': 'Xiaomi Redmi Note 12 Pro',
'POCO X6 Pro': 'Xiaomi POCO X6 Pro',
'POCO F5': 'Xiaomi POCO F5',
// OnePlus models
'LE2125': 'OnePlus 9 Pro',
'LE2123': 'OnePlus 9',
'IN2023': 'OnePlus 8 Pro',
'IN2013': 'OnePlus 8',
'HD1913': 'OnePlus 7T Pro',
'HD1903': 'OnePlus 7T',
'GM1913': 'OnePlus 7 Pro',
'GM1903': 'OnePlus 7',
'CPH2423': 'OnePlus 11',
'CPH2449': 'OnePlus 11R',
'CPH2411': 'OnePlus 10 Pro',
'CPH2413': 'OnePlus 10T',
// Huawei models (newer models might not have Google services)
'VOG-L29': 'Huawei P30 Pro',
'ELE-L29': 'Huawei P30',
'CLT-L29': 'Huawei P20 Pro',
'EML-L29': 'Huawei P20',
'LYA-L29': 'Huawei Mate 20 Pro',
'HMA-L29': 'Huawei Mate 20',
'NOH-NX9': 'Huawei Mate 60 Pro',
'BRA-NX9': 'Huawei Mate 60',
// Sony models
'XQ-DQ54': 'Sony Xperia 1 V',
'XQ-CQ54': 'Sony Xperia 5 V',
'XQ-BQ52': 'Sony Xperia 1 IV',
'XQ-AS52': 'Sony Xperia 5 IV',
'XQ-BC52': 'Sony Xperia 1 III',
'XQ-AS62': 'Sony Xperia 5 III',
// OPPO models
'CPH2437': 'OPPO Find X6 Pro',
'CPH2305': 'OPPO Find X5 Pro',
'CPH2173': 'OPPO Find X3 Pro',
'CPH2487': 'OPPO Reno 11 Pro',
'CPH2481': 'OPPO Reno 10 Pro+',
// Vivo models
'V2302': 'Vivo X100 Pro',
'V2250': 'Vivo X90 Pro',
'V2145': 'Vivo X80 Pro',
'V2183A': 'Vivo X70 Pro+',
// Realme models
'RMX3771': 'Realme GT 5',
'RMX3708': 'Realme GT 3',
'RMX3300': 'Realme GT 2 Pro',
'RMX3360': 'Realme GT Neo 3',
// Nothing models
'A063': 'Nothing Phone (2)',
'A013': 'Nothing Phone (1)',
// ASUS models
'ASUS_AI2302': 'ASUS Zenfone 10',
'ASUS_AI2202': 'ASUS Zenfone 9',
'ASUS_I006D': 'ASUS ROG Phone 7',
'ASUS_I005D': 'ASUS ROG Phone 6'
};
// Function to get device name from model
function getDeviceFromModel(model) {
if (!model) return null;
// Direct match
if (deviceModelMapping[model]) {
return deviceModelMapping[model];
}
// Try to match partial model names
for (const [key, value] of Object.entries(deviceModelMapping)) {
if (model.includes(key) || key.includes(model)) {
return value;
}
}
// Check if it's a generic Apple device
if (model.includes('iPhone') || model.includes('iPad')) {
return model; // Return as is, it's already descriptive
}
return null;
}
// Format metadata keys for display
function formatMetadataKey(key) {
// Remove common prefixes and format
key = key.replace(/^(EXIF|GPS|IPTC|XMP)/, '');
// Special formatting for known keys - do first before adding spaces
const keyMap = {
'Make': 'Kamera-Hersteller',
'Model': 'Kamera-Modell',
'DateTime Original': 'Aufnahmedatum',
'DateTimeOriginal': 'Aufnahmedatum',
'Exposure Time': 'Belichtungszeit',
'ExposureTime': 'Belichtungszeit',
'F Number': 'Blende',
'FNumber': 'Blende',
'ISO Speed Ratings': 'ISO-Wert',
'ISOSpeedRatings': 'ISO-Wert',
'Focal Length': 'Brennweite',
'FocalLength': 'Brennweite',
'Flash': 'Blitz',
'GPS Latitude': 'Breitengrad',
'GPSLatitude': 'Breitengrad',
'GPS Longitude': 'Längengrad',
'GPSLongitude': 'Längengrad',
'GPS Altitude': 'Höhe',
'GPSAltitude': 'Höhe',
'Lens Model': 'Objektiv',
'LensModel': 'Objektiv',
'White Balance': 'Weißabgleich',
'WhiteBalance': 'Weißabgleich',
'Exposure Mode': 'Belichtungsmodus',
'ExposureMode': 'Belichtungsmodus',
'Color Space': 'Farbraum',
'ColorSpace': 'Farbraum'
};
// Check if we have a known key first
if (keyMap[key]) {
return keyMap[key];
}
// Add spaces before capital letters but preserve acronyms
key = key.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2'); // For cases like "GPSLatitude" -> "GPS Latitude"
key = key.replace(/([a-z\d])([A-Z])/g, '$1 $2'); // For cases like "dateTime" -> "date Time"
key = key.trim();
return keyMap[key] || key;
}
// Format metadata values for display
function formatMetadataValue(key, value) {
// Handle camera model with device mapping
if (key.includes('Model') && !key.includes('Lens')) {
const device = getDeviceFromModel(value);
if (device && device !== value) {
return `${value} (${device})`;
}
}
// Format GPS coordinates
if (key.includes('GPS') && (key.includes('Latitude') || key.includes('Longitude'))) {
if (typeof value === 'number') {
return value.toFixed(6) + '°';
}
}
// Format altitude
if (key.includes('Altitude') && typeof value === 'number') {
return value.toFixed(2) + ' m';
}
// Format focal length
if (key.includes('FocalLength') && typeof value === 'number') {
return value + ' mm';
}
// Format dates
if (key.includes('Date') && value.includes(':')) {
try {
const date = new Date(value.replace(/^(\d{4}):(\d{2}):(\d{2})/, '$1-$2-$3'));
if (!isNaN(date)) {
return date.toLocaleString('de-DE');
}
} catch (e) {}
}
return value;
}
// Add toggle handlers for collapsible sections
function addSectionToggleHandlers() {
const headers = document.querySelectorAll('.metadata-section-header');
headers.forEach(header => {
header.addEventListener('click', function() {
const section = this.parentElement;
section.classList.toggle('collapsed');
});
});
}
// Clear metadata and reset
function clearMetadata() {
metadataContainer.classList.remove('active');
fileInput.value = '';
imagePreview.innerHTML = '';
metadataContent.innerHTML = '';
// Clear stored data
currentFileData = null;
currentMetadata = null;
currentPreviewUrl = null;
}
// Utility functions
function formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
function formatDuration(seconds) {
const h = Math.floor(seconds / 3600);
const m = Math.floor((seconds % 3600) / 60);
const s = Math.floor(seconds % 60);
if (h > 0) {
return `${h}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
}
return `${m}:${s.toString().padStart(2, '0')}`;
}
// Store current file data for report generation
let currentFileData = null;
let currentMetadata = null;
let currentPreviewUrl = null;
// Get tooltips for metadata fields
function getMetadataTooltip(key, formattedKey) {
const tooltips = {
'ISO-Wert': 'Die Lichtempfindlichkeit des Kamerasensors. Höhere Werte (z.B. 1600) bedeuten mehr Empfindlichkeit, aber auch mehr Bildrauschen.',
'Blende': 'Die Öffnung des Objektivs (f-Zahl). Kleinere Zahlen (z.B. f/1.8) bedeuten eine größere Öffnung und mehr Licht.',
'Belichtungszeit': 'Wie lange der Kamerasensor dem Licht ausgesetzt ist. Z.B. 1/100 bedeutet eine Hundertstelsekunde.',
'Brennweite': 'Der Abstand zwischen Objektiv und Sensor in Millimetern. Bestimmt den Bildausschnitt.',
'Weißabgleich': 'Einstellung zur korrekten Farbwiedergabe unter verschiedenen Lichtbedingungen.',
'Belichtungsmodus': 'Automatik, Manuell, Blendenpriorität oder Zeitpriorität - bestimmt wie die Kamera die Belichtung steuert.',
'Farbraum': 'Definiert den Bereich der darstellbaren Farben (z.B. sRGB für Web, Adobe RGB für Druck).',
'Breitengrad': 'GPS-Koordinate für die Nord-Süd-Position des Aufnahmeortes.',
'Längengrad': 'GPS-Koordinate für die Ost-West-Position des Aufnahmeortes.',
'Höhe': 'Höhe über dem Meeresspiegel am Aufnahmeort.',
'Objektiv': 'Das verwendete Kameraobjektiv mit seinen technischen Eigenschaften.',
'Blitz': 'Informationen darüber, ob und wie der Blitz verwendet wurde.',
'ExposureCompensation': 'Belichtungskorrektur - manuelle Anpassung der automatischen Belichtung.',
'MeteringMode': 'Messmethode für die Belichtung (Spot, Matrix, Mittenbetont).',
'FocalLengthIn35mmFilm': 'Brennweite umgerechnet auf Kleinbildformat für bessere Vergleichbarkeit.',
'DigitalZoomRatio': 'Digitaler Zoom-Faktor - elektronische Vergrößerung mit Qualitätsverlust.',
'SceneCaptureType': 'Art der Aufnahme (Landschaft, Portrait, Nachtaufnahme).',
'SubjectDistanceRange': 'Entfernungsbereich zum Motiv (Makro, Nah, Fern).',
'Sharpness': 'Nachschärfung - digitale Kantenbetonung des Bildes.',
'Saturation': 'Farbsättigung - Intensität der Farben im Bild.',
'Contrast': 'Kontrast - Unterschied zwischen hellen und dunklen Bereichen.',
'GainControl': 'Verstärkungsregelung - elektronische Signalverstärkung bei wenig Licht.',
// Neue Erklärungen
'ApertureValue': 'Blendenwert in APEX-Einheiten - technische Darstellung der Blendenöffnung für Berechnungen.',
'Aperture Value': 'Blendenwert in APEX-Einheiten - technische Darstellung der Blendenöffnung für Berechnungen.',
'ExposureBiasValue': 'Belichtungskorrektur in EV-Stufen. +1 bedeutet doppelt so hell, -1 halb so hell.',
'Exposure Bias Value': 'Belichtungskorrektur in EV-Stufen. +1 bedeutet doppelt so hell, -1 halb so hell.',
'MaxApertureValue': 'Größte Blendenöffnung des Objektivs (kleinste f-Zahl) - bestimmt die maximale Lichtmenge.',
'Max Aperture Value': 'Größte Blendenöffnung des Objektivs (kleinste f-Zahl) - bestimmt die maximale Lichtmenge.',
'ExposureProgram': 'Kameraeinstellung: 0=Unbekannt, 1=Manuell, 2=Normal, 3=Blendenpriorität, 4=Zeitpriorität, 5=Kreativ, 6=Action, 7=Portrait, 8=Landschaft.',
'Exposure Program': 'Kameraeinstellung: 0=Unbekannt, 1=Manuell, 2=Normal, 3=Blendenpriorität, 4=Zeitpriorität, 5=Kreativ, 6=Action, 7=Portrait, 8=Landschaft.',
'SubSampling': 'Farbunterabtastung bei JPEG - bestimmt wie Farbinformationen gespeichert werden (4:2:2 oder 4:2:0).',
'YCbCrSubSampling': 'Farbunterabtastung bei JPEG - bestimmt wie Farbinformationen gespeichert werden (4:2:2 oder 4:2:0).',
'Orientation': 'Bildausrichtung: 1=Normal, 3=180° gedreht, 6=90° rechts gedreht, 8=90° links gedreht.',
'YCbCrPositioning': 'Position der Farbinformationen: 1=Zentriert (üblich), 2=Co-sited (professionell).',
'Y Cb Cr Positioning': 'Position der Farbinformationen: 1=Zentriert (üblich), 2=Co-sited (professionell).',
'ExifIFDPointer': 'Technischer Verweis auf den Speicherort der EXIF-Daten in der Datei.',
'EXIF IFD Pointer': 'Technischer Verweis auf den Speicherort der EXIF-Daten in der Datei.',
'ExifVersion': 'Version des EXIF-Standards (z.B. 0232 = Version 2.32) - bestimmt verfügbare Metadatenfelder.',
'EXIF Version': 'Version des EXIF-Standards (z.B. 0232 = Version 2.32) - bestimmt verfügbare Metadatenfelder.',
'ShutterSpeedValue': 'Verschlusszeit in APEX-Einheiten - technische Darstellung für Berechnungen.',
'Shutter Speed Value': 'Verschlusszeit in APEX-Einheiten - technische Darstellung für Berechnungen.',
'BrightnessValue': 'Objekthelligkeit in APEX-Einheiten - Messwert der Motivhelligkeit.',
'Brightness Value': 'Objekthelligkeit in APEX-Einheiten - Messwert der Motivhelligkeit.',
'OffsetTime': 'Zeitzonenversatz zur UTC-Zeit bei der Aufnahme (z.B. +02:00 für MESZ).',
'Offset Time': 'Zeitzonenversatz zur UTC-Zeit bei der Aufnahme (z.B. +02:00 für MESZ).',
'OffsetTimeOriginal': 'Ursprünglicher Zeitzonenversatz bei der Aufnahme - bleibt bei Bearbeitung erhalten.',
'Offset Time Original': 'Ursprünglicher Zeitzonenversatz bei der Aufnahme - bleibt bei Bearbeitung erhalten.',
'ImageUniqueID': 'Einzigartige Bild-ID aus Datum, Kameraseriennummer und Bildnummer - identifiziert das Bild eindeutig.',
'Image Unique ID': 'Einzigartige Bild-ID aus Datum, Kameraseriennummer und Bildnummer - identifiziert das Bild eindeutig.'
};
// Check both the original key and formatted key
return tooltips[formattedKey] || tooltips[key] || null;
}
// Generate PDF report
async function generateReport() {
if (!currentFileData) {
alert('Keine Datei zum Erstellen eines Berichts vorhanden.');
return;
}
try {
// Create loading indicator
const originalText = document.getElementById('reportBtn').textContent;
document.getElementById('reportBtn').textContent = 'Bericht wird erstellt...';
document.getElementById('reportBtn').disabled = true;
// Import jsPDF and AutoTable dynamically
const jsPDFScript = document.createElement('script');
jsPDFScript.src = 'https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js';
document.head.appendChild(jsPDFScript);
jsPDFScript.onload = async () => {
// Load AutoTable plugin
const autoTableScript = document.createElement('script');
autoTableScript.src = 'https://cdnjs.cloudflare.com/ajax/libs/jspdf-autotable/3.8.1/jspdf.plugin.autotable.min.js';
document.head.appendChild(autoTableScript);
autoTableScript.onload = async () => {
const { jsPDF } = window.jspdf;
const doc = new jsPDF();
// Set fonts
doc.setFont('helvetica');
// Add header
doc.setFontSize(20);
doc.setTextColor(35, 45, 83); // Primary color
doc.text('Metadaten-Bericht', 105, 20, { align: 'center' });
// Add generation date
doc.setFontSize(10);
doc.setTextColor(100);
const now = new Date();
doc.text(`Erstellt am: ${now.toLocaleString('de-DE')}`, 105, 30, { align: 'center' });
// Add file info section
let yPosition = 50;
doc.setFontSize(14);
doc.setTextColor(35, 45, 83);
doc.text('Datei-Informationen', 20, yPosition);
yPosition += 5;
// Basic file info table data
const fileInfoData = [
['Dateiname', currentFileData.name],
['Größe', currentFileData.size],
['Typ', currentFileData.type],
['Zuletzt geändert', currentFileData.lastModified]
];
if (currentFileData.width) {
fileInfoData.push(['Auflösung', `${currentFileData.width} × ${currentFileData.height}`]);
fileInfoData.push(['Seitenverhältnis', `${currentFileData.aspect}:1`]);
}
if (currentFileData.duration) {
fileInfoData.push(['Dauer', currentFileData.duration]);
}
// Create table for file info
doc.autoTable({
startY: yPosition,
head: [],
body: fileInfoData,
theme: 'grid',
styles: {
fontSize: 10,
cellPadding: 5
},
columnStyles: {
0: {
fontStyle: 'bold',
fillColor: [245, 245, 245],
textColor: [35, 45, 83],
cellWidth: 50
}
},
margin: { left: 20, right: 20 }
});
yPosition = doc.lastAutoTable.finalY + 10;
// Add thumbnail if it's an image
if (currentPreviewUrl && currentFileData.type.startsWith('image/')) {
try {
// Check if we have enough space on current page
const remainingSpace = doc.internal.pageSize.getHeight() - yPosition - 40;
if (remainingSpace < 100) { // If less than 100 units remaining, start new page
doc.addPage();
yPosition = 20;
}
yPosition += 10;
doc.setFontSize(14);
doc.setTextColor(35, 45, 83);
// Center the "Vorschau" text
const pageWidth = doc.internal.pageSize.getWidth();
doc.text('Vorschau', pageWidth / 2, yPosition, { align: 'center' });
yPosition += 10;
// Get image element
const img = document.querySelector('.image-preview img');
if (img) {
// Convert image to base64 with high quality
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Use higher resolution for better quality
const maxWidth = 800;
const maxHeight = 800;
let width = img.naturalWidth;
let height = img.naturalHeight;
// Calculate aspect ratio
const ratio = Math.min(maxWidth / width, maxHeight / height);
if (ratio < 1) {
width = width * ratio;
height = height * ratio;
}
canvas.width = width;
canvas.height = height;
// Enable image smoothing for better quality
ctx.imageSmoothingEnabled = true;
ctx.imageSmoothingQuality = 'high';
ctx.drawImage(img, 0, 0, width, height);
// Use higher quality JPEG compression
const dataUrl = canvas.toDataURL('image/jpeg', 0.95);
// Calculate display size to fit on page
const pageWidth = doc.internal.pageSize.getWidth();
const pageHeight = doc.internal.pageSize.getHeight();
const maxDisplayWidth = pageWidth - 40; // 20px margin on each side
const maxDisplayHeight = pageHeight - yPosition - 40; // Leave space for footer
// Start with desired width
let displayWidth = 80; // Smaller default size
let displayHeight = (height / width) * displayWidth;
// Check if it fits vertically, if not scale down
if (displayHeight > maxDisplayHeight) {
displayHeight = maxDisplayHeight;
displayWidth = (width / height) * displayHeight;
}
// Check if it fits horizontally, if not scale down
if (displayWidth > maxDisplayWidth) {
displayWidth = maxDisplayWidth;
displayHeight = (height / width) * displayWidth;
}
// Center the image horizontally
const xPosition = (pageWidth - displayWidth) / 2;
doc.addImage(dataUrl, 'JPEG', xPosition, yPosition, displayWidth, displayHeight);
yPosition += displayHeight + 10;
}
} catch (error) {
console.error('Fehler beim Hinzufügen des Thumbnails:', error);
}
}
// Add metadata sections
if (currentMetadata && Object.keys(currentMetadata).length > 0) {
// Check if we need a new page
if (yPosition > 200) {
doc.addPage();
yPosition = 20;
}
Object.entries(currentMetadata).forEach(([section, data]) => {
if (Object.keys(data).length > 0) {
// Check if we need a new page
if (yPosition > 240) {
doc.addPage();
yPosition = 20;
}
// Section header
doc.setFontSize(12);
doc.setTextColor(35, 45, 83);
doc.text(section, 20, yPosition);
yPosition += 5;
// Convert data to table format
const tableData = Object.entries(data).map(([key, value]) => {
// Format the key
const formattedKey = formatMetadataKey(key);
// Ensure value is string and not too long
const displayValue = String(value).length > 100 ? String(value).substring(0, 100) + '...' : String(value);
return [formattedKey, displayValue];
});
// Create table for this section
doc.autoTable({
startY: yPosition,
head: [],
body: tableData,
theme: 'grid',
styles: {
fontSize: 9,
cellPadding: 3
},
columnStyles: {
0: {
fontStyle: 'bold',
fillColor: [245, 245, 245],
textColor: [35, 45, 83],
cellWidth: 60
},
1: {
cellWidth: 'auto'
}
},
margin: { left: 20, right: 20 },
didDrawPage: function(data) {
// Header on new pages
if (data.pageNumber > 1) {
doc.setFontSize(12);
doc.setTextColor(35, 45, 83);
doc.text(section + ' (Fortsetzung)', 20, 15);
}
}
});
yPosition = doc.lastAutoTable.finalY + 10;
}
});
}
// Add footer
const pageCount = doc.internal.getNumberOfPages();
for (let i = 1; i <= pageCount; i++) {
doc.setPage(i);
doc.setFontSize(8);
doc.setTextColor(150);
doc.text(`Seite ${i} von ${pageCount}`, 105, 285, { align: 'center' });
doc.text('Metadaten-Crawler © 2025 IntelSight', 105, 290, { align: 'center' });
}
// Save the PDF
doc.save(`Metadaten-Bericht_${currentFileData.name.replace(/\.[^/.]+$/, '')}_${now.getTime()}.pdf`);
// Restore button
document.getElementById('reportBtn').textContent = originalText;
document.getElementById('reportBtn').disabled = false;
};
autoTableScript.onerror = () => {
alert('Fehler beim Laden der PDF-Tabellen-Bibliothek.');
document.getElementById('reportBtn').textContent = originalText;
document.getElementById('reportBtn').disabled = false;
};
};
jsPDFScript.onerror = () => {
alert('Fehler beim Laden der PDF-Bibliothek.');
document.getElementById('reportBtn').textContent = originalText;
document.getElementById('reportBtn').disabled = false;
};
} catch (error) {
console.error('Fehler beim Erstellen des Berichts:', error);
alert('Fehler beim Erstellen des PDF-Berichts.');
document.getElementById('reportBtn').textContent = 'Bericht erstellen';
document.getElementById('reportBtn').disabled = false;
}
}