// === 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 = `

${basicInfo.name}

Größe:${basicInfo.size} Typ:${basicInfo.type} Zuletzt geändert:${basicInfo.lastModified} Auflösung:${basicInfo.width} × ${basicInfo.height} Seitenverhältnis:${basicInfo.aspect}:1
`; // Show preview imagePreview.innerHTML = `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 = '
Keine zusätzlichen Metadaten gefunden.
'; } 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 = `

${basicInfo.name}

Größe:${basicInfo.size} Typ:${basicInfo.type} Zuletzt geändert:${basicInfo.lastModified} Dauer:${basicInfo.duration} Auflösung:${basicInfo.width} × ${basicInfo.height} Seitenverhältnis:${basicInfo.aspect}:1
`; // Show video preview (poster frame) imagePreview.innerHTML = ` `; // 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 += '
Erweiterte Video-Metadaten können im Browser nur begrenzt ausgelesen werden.
'; // 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 = `
${icon} ${title}
`; for (const [key, value] of Object.entries(data)) { const formattedKey = formatMetadataKey(key); const formattedValue = formatMetadataValue(key, value); const tooltip = getMetadataTooltip(key, formattedKey); html += ` `; } html += `
`; 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; } }