UNPKG

image-asset-manager

Version:

A comprehensive image asset management tool for frontend projects

247 lines 9.69 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.MetadataExtractor = void 0; const fs = __importStar(require("fs/promises")); const sharp_1 = __importDefault(require("sharp")); const types_1 = require("../types"); class MetadataExtractor { /** * Extract comprehensive metadata from an image file */ async extractMetadata(file) { // Handle SVG files separately since Sharp doesn't handle SVG metadata well if (file.extension === ".svg") { return this.extractSvgMetadata(file); } try { const metadata = await this.extractImageMetadata(file.path); // Update the file object with dimensions file.dimensions = { width: metadata.width || 0, height: metadata.height || 0, }; return { colorPalette: [], // Will be implemented if needed hasTransparency: metadata.hasAlpha || false, isAnimated: metadata.pages ? metadata.pages > 1 : false, compressionLevel: this.estimateCompressionLevel(metadata, file.size), }; } catch (error) { throw new types_1.ImageAssetError(types_1.ErrorCode.INVALID_IMAGE_FORMAT, `Failed to extract metadata from: ${file.path}`, error, true); } } /** * Extract metadata using Sharp library */ async extractImageMetadata(filePath) { try { const image = (0, sharp_1.default)(filePath); return await image.metadata(); } catch (error) { throw new types_1.ImageAssetError(types_1.ErrorCode.INVALID_IMAGE_FORMAT, `Sharp failed to process image: ${filePath}`, error, true); } } /** * Extract metadata from SVG files (Sharp doesn't handle SVG metadata well) */ async extractSvgMetadata(file) { try { const content = await fs.readFile(file.path, "utf-8"); const dimensions = this.parseSvgDimensions(content); // Update file dimensions file.dimensions = dimensions; return { colorPalette: [], hasTransparency: true, // SVG typically supports transparency isAnimated: content.includes("<animate") || content.includes("<animateTransform"), compressionLevel: this.estimateSvgCompression(content, file.size), }; } catch (error) { // Return default metadata if SVG parsing fails return { colorPalette: [], hasTransparency: true, isAnimated: false, compressionLevel: 0, }; } } /** * Parse SVG dimensions from content */ parseSvgDimensions(svgContent) { // Try to extract width and height from SVG tag const svgTagMatch = svgContent.match(/<svg[^>]*>/i); if (!svgTagMatch) { return { width: 0, height: 0 }; } const svgTag = svgTagMatch[0]; // Extract width and height attributes const widthMatch = svgTag.match(/width\s*=\s*["']?(\d+(?:\.\d+)?)/i); const heightMatch = svgTag.match(/height\s*=\s*["']?(\d+(?:\.\d+)?)/i); // Extract viewBox as fallback const viewBoxMatch = svgTag.match(/viewBox\s*=\s*["']?[^\d]*(\d+(?:\.\d+)?)\s+(\d+(?:\.\d+)?)\s+(\d+(?:\.\d+)?)\s+(\d+(?:\.\d+)?)/i); let width = 0; let height = 0; if (widthMatch && heightMatch) { width = parseFloat(widthMatch[1]); height = parseFloat(heightMatch[1]); } else if (viewBoxMatch) { // Use viewBox width and height (3rd and 4th values) width = parseFloat(viewBoxMatch[3]); height = parseFloat(viewBoxMatch[4]); } return { width, height }; } /** * Estimate compression level based on metadata and file size */ estimateCompressionLevel(metadata, fileSize) { if (!metadata.width || !metadata.height) { return 0; } // Calculate theoretical uncompressed size (assuming 4 bytes per pixel for RGBA) const theoreticalSize = metadata.width * metadata.height * 4; // Calculate compression ratio const compressionRatio = fileSize / theoreticalSize; // Convert to a 0-100 scale (lower number = better compression) return Math.min(100, Math.max(0, Math.round(compressionRatio * 100))); } /** * Estimate SVG compression level */ estimateSvgCompression(content, fileSize) { // For SVG, we can estimate compression by looking at content density const contentLength = content.length; const ratio = fileSize / contentLength; // SVG files are typically well-compressed if ratio is close to 1 return Math.round((1 - ratio) * 100); } /** * Batch extract metadata for multiple files */ async extractBatchMetadata(files) { const metadataMap = new Map(); const promises = files.map(async (file) => { try { const metadata = await this.extractMetadata(file); metadataMap.set(file.id, metadata); } catch (error) { console.warn(`Failed to extract metadata for ${file.path}:`, error); // Set default metadata for failed extractions metadataMap.set(file.id, { colorPalette: [], hasTransparency: false, isAnimated: false, compressionLevel: 0, }); } }); await Promise.all(promises); return metadataMap; } /** * Enhanced categorization based on metadata and file properties */ categorizeImageWithMetadata(file, metadata) { const { width, height } = file.dimensions; const aspectRatio = width > 0 && height > 0 ? width / height : 1; const area = width * height; const pathLower = file.relativePath.toLowerCase(); // Size-based categorization if (area < 1024) { // Very small images (32x32 or smaller) return "icons"; } if (area < 10000) { // Small images (100x100 or smaller) if (pathLower.includes("icon") || pathLower.includes("ico")) { return "icons"; } if (pathLower.includes("thumb") || pathLower.includes("preview")) { return "thumbnails"; } return "small-graphics"; } // Aspect ratio based categorization if (aspectRatio > 2.5) { // Very wide images if (pathLower.includes("banner") || pathLower.includes("header")) { return "banners"; } return "wide-graphics"; } if (aspectRatio < 0.4) { // Very tall images return "vertical-graphics"; } // Large images if (area > 500000) { // Large images (roughly 700x700+) if (pathLower.includes("background") || pathLower.includes("bg")) { return "backgrounds"; } if (pathLower.includes("hero") || pathLower.includes("banner")) { return "hero-images"; } return "large-graphics"; } // Path-based categorization (fallback to existing logic) if (pathLower.includes("logo")) return "logos"; if (pathLower.includes("avatar") || pathLower.includes("profile")) return "avatars"; if (pathLower.includes("gallery") || pathLower.includes("photo")) return "gallery"; if (pathLower.includes("ui") || pathLower.includes("component")) return "ui-components"; // Animation-based categorization if (metadata.isAnimated) { return "animated"; } return "general"; } } exports.MetadataExtractor = MetadataExtractor; //# sourceMappingURL=MetadataExtractor.js.map