image-asset-manager
Version:
A comprehensive image asset management tool for frontend projects
247 lines • 9.69 kB
JavaScript
;
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