feat: add ImageHandler with EXIF extraction and comprehensive MIME detection (v5.2.0)
Implements Phase 1.5 (Comprehensive MIME Type Detection) and adds built-in image processing support to IntelligentImportAugmentation. **New Features:** - ImageHandler: Extracts image metadata (dimensions, format, color space) using sharp - EXIF extraction: Camera data, GPS, timestamps using exifr library - Support for JPEG, PNG, WebP, GIF, TIFF, BMP, SVG, HEIC, AVIF formats - MimeTypeDetector: Unified MIME type detection with magic byte support - FormatDetector: Enhanced with image format detection via MIME + magic bytes **Architecture Fixes:** - Fixed brain.import() augmentation pipeline integration (src/brainy.ts:3140-3154) - Added parameter spreading for ImportSource objects to enable augmentation access - Fixed metadata propagation through ImportCoordinator to final results - Added augmentation data check in ImportCoordinator.extract() **Integration:** - ImageHandler registered as built-in handler alongside CSV, Excel, PDF - Images import as 'media' entities with 'image' subtype - Full metadata preserved in knowledge graph entities - Configuration options: enableImage, extractEXIF, imageDefaults **Test Coverage:** - 15 integration tests (image-import.test.ts) - 100% passing - 27 unit tests (image-handler.test.ts) - 100% passing - Format detection tests for all supported image types - Error handling and resilience tests **Breaking Changes:** None - backward compatible Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
6345f87eb2
commit
1874b77896
26 changed files with 5079 additions and 434 deletions
|
|
@ -2,14 +2,17 @@
|
|||
* Format Detector
|
||||
*
|
||||
* Unified format detection for all import types using:
|
||||
* - MIME type detection (via MimeTypeDetector service)
|
||||
* - Magic byte signatures (PDF, Excel, images)
|
||||
* - File extensions
|
||||
* - File extensions (via MimeTypeDetector)
|
||||
* - Content analysis (JSON, Markdown, CSV)
|
||||
*
|
||||
* NO MOCKS - Production-ready implementation
|
||||
*/
|
||||
|
||||
export type SupportedFormat = 'excel' | 'pdf' | 'csv' | 'json' | 'markdown' | 'yaml' | 'docx'
|
||||
import { mimeDetector } from '../vfs/MimeTypeDetector.js'
|
||||
|
||||
export type SupportedFormat = 'excel' | 'pdf' | 'csv' | 'json' | 'markdown' | 'yaml' | 'docx' | 'image'
|
||||
|
||||
export interface DetectionResult {
|
||||
format: SupportedFormat
|
||||
|
|
@ -38,36 +41,84 @@ export class FormatDetector {
|
|||
|
||||
/**
|
||||
* Detect format from file path
|
||||
*
|
||||
* Uses MimeTypeDetector (2000+ types) and maps to SupportedFormat
|
||||
*/
|
||||
detectFromPath(path: string): DetectionResult | null {
|
||||
const ext = this.getExtension(path).toLowerCase()
|
||||
// Get MIME type from MimeTypeDetector
|
||||
const mimeType = mimeDetector.detectMimeType(path)
|
||||
|
||||
const extensionMap: Record<string, SupportedFormat> = {
|
||||
'.xlsx': 'excel',
|
||||
'.xls': 'excel',
|
||||
'.pdf': 'pdf',
|
||||
'.csv': 'csv',
|
||||
'.json': 'json',
|
||||
'.md': 'markdown',
|
||||
'.markdown': 'markdown',
|
||||
'.yaml': 'yaml',
|
||||
'.yml': 'yaml',
|
||||
'.docx': 'docx',
|
||||
'.doc': 'docx'
|
||||
}
|
||||
|
||||
const format = extensionMap[ext]
|
||||
// Map MIME type to SupportedFormat
|
||||
const format = this.mimeTypeToFormat(mimeType)
|
||||
if (format) {
|
||||
const ext = this.getExtension(path)
|
||||
return {
|
||||
format,
|
||||
confidence: 0.9,
|
||||
evidence: [`File extension: ${ext}`]
|
||||
evidence: [`MIME type: ${mimeType}`, `File extension: ${ext}`]
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Map MIME type to SupportedFormat
|
||||
*
|
||||
* Supports all variations of Excel, PDF, CSV, JSON, Markdown, YAML, DOCX
|
||||
*/
|
||||
private mimeTypeToFormat(mimeType: string): SupportedFormat | null {
|
||||
// Excel formats (Office Open XML + legacy)
|
||||
if (
|
||||
mimeType === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ||
|
||||
mimeType === 'application/vnd.ms-excel' ||
|
||||
mimeType === 'application/vnd.ms-excel.sheet.macroEnabled.12' ||
|
||||
mimeType === 'application/vnd.openxmlformats-officedocument.spreadsheetml.template'
|
||||
) {
|
||||
return 'excel'
|
||||
}
|
||||
|
||||
// PDF
|
||||
if (mimeType === 'application/pdf') {
|
||||
return 'pdf'
|
||||
}
|
||||
|
||||
// CSV
|
||||
if (mimeType === 'text/csv') {
|
||||
return 'csv'
|
||||
}
|
||||
|
||||
// JSON
|
||||
if (mimeType === 'application/json') {
|
||||
return 'json'
|
||||
}
|
||||
|
||||
// Markdown
|
||||
if (mimeType === 'text/markdown' || mimeType === 'text/x-markdown') {
|
||||
return 'markdown'
|
||||
}
|
||||
|
||||
// YAML
|
||||
if (mimeType === 'text/yaml' || mimeType === 'text/x-yaml' || mimeType === 'application/x-yaml') {
|
||||
return 'yaml'
|
||||
}
|
||||
|
||||
// Word documents (Office Open XML)
|
||||
if (
|
||||
mimeType === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ||
|
||||
mimeType === 'application/msword'
|
||||
) {
|
||||
return 'docx'
|
||||
}
|
||||
|
||||
// Images (v5.2.0: ImageHandler support)
|
||||
if (mimeType.startsWith('image/')) {
|
||||
return 'image'
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect format from string content
|
||||
*/
|
||||
|
|
@ -155,6 +206,55 @@ export class FormatDetector {
|
|||
}
|
||||
}
|
||||
|
||||
// Image formats (v5.2.0)
|
||||
// JPEG: FF D8 FF
|
||||
if (buffer[0] === 0xFF && buffer[1] === 0xD8 && buffer[2] === 0xFF) {
|
||||
return {
|
||||
format: 'image',
|
||||
confidence: 1.0,
|
||||
evidence: ['JPEG magic bytes: FF D8 FF']
|
||||
}
|
||||
}
|
||||
|
||||
// PNG: 89 50 4E 47
|
||||
if (buffer[0] === 0x89 && buffer[1] === 0x50 && buffer[2] === 0x4E && buffer[3] === 0x47) {
|
||||
return {
|
||||
format: 'image',
|
||||
confidence: 1.0,
|
||||
evidence: ['PNG magic bytes: 89 50 4E 47']
|
||||
}
|
||||
}
|
||||
|
||||
// GIF: 47 49 46 38
|
||||
if (buffer[0] === 0x47 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x38) {
|
||||
return {
|
||||
format: 'image',
|
||||
confidence: 1.0,
|
||||
evidence: ['GIF magic bytes: GIF8']
|
||||
}
|
||||
}
|
||||
|
||||
// BMP: 42 4D
|
||||
if (buffer[0] === 0x42 && buffer[1] === 0x4D) {
|
||||
return {
|
||||
format: 'image',
|
||||
confidence: 1.0,
|
||||
evidence: ['BMP magic bytes: BM']
|
||||
}
|
||||
}
|
||||
|
||||
// WebP: RIFF....WEBP
|
||||
if (buffer[0] === 0x52 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x46 && buffer.length >= 12) {
|
||||
const webpCheck = buffer.toString('utf8', 8, 12)
|
||||
if (webpCheck === 'WEBP') {
|
||||
return {
|
||||
format: 'image',
|
||||
confidence: 1.0,
|
||||
evidence: ['WebP magic bytes: RIFF...WEBP']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue