@julesl23/s5js
Version:
Enhanced TypeScript SDK for S5 decentralized storage with path-based API, media processing, and directory utilities
249 lines • 9.52 kB
JavaScript
import { BrowserCompat } from './compat/browser.js';
import { WASMModule as WASMModuleImpl } from './wasm/module.js';
import { CanvasMetadataExtractor } from './fallback/canvas.js';
import { ThumbnailGenerator } from './thumbnail/generator.js';
import { ProgressiveImageLoader } from './progressive/loader.js';
// Export browser compatibility checker
export { BrowserCompat };
// Export thumbnail generator
export { ThumbnailGenerator };
// Export progressive image loader
export { ProgressiveImageLoader };
/**
* Main media processing class with lazy WASM loading
*/
export class MediaProcessor {
static wasmModule;
static loadingPromise;
static initialized = false;
static processingStrategy;
/**
* Initialize the MediaProcessor and load WASM module
*/
static async initialize(options) {
if (this.initialized)
return;
// Detect browser capabilities and select processing strategy
const capabilities = await BrowserCompat.checkCapabilities();
this.processingStrategy = BrowserCompat.selectProcessingStrategy(capabilities);
// Load WASM module if the strategy includes WASM
const shouldLoadWASM = this.processingStrategy.includes('wasm');
if (shouldLoadWASM) {
if (!this.loadingPromise) {
this.loadingPromise = this.loadWASM(options);
}
this.wasmModule = await this.loadingPromise;
}
this.initialized = true;
}
/**
* Load the WASM module dynamically
*/
static async loadWASM(options) {
// Report initial progress
options?.onProgress?.(0);
try {
// Load the real WASM module
const wasmModule = await WASMModuleImpl.initialize(options);
return wasmModule;
}
catch (error) {
// Expected when WASM not available - use Canvas fallback
if (process.env.DEBUG) {
console.warn('WASM not available, using Canvas fallback:', error);
}
// Return a fallback that uses Canvas API
return {
async initialize() {
// No-op for canvas fallback
},
extractMetadata(data) {
// Convert Uint8Array to Blob for Canvas API
// Try to detect format from magic bytes
let mimeType = 'application/octet-stream';
if (data.length >= 4) {
if (data[0] === 0xFF && data[1] === 0xD8) {
mimeType = 'image/jpeg';
}
else if (data[0] === 0x89 && data[1] === 0x50 && data[2] === 0x4E && data[3] === 0x47) {
mimeType = 'image/png';
}
else if (data[0] === 0x47 && data[1] === 0x49 && data[2] === 0x46) {
mimeType = 'image/gif';
}
else if (data[0] === 0x42 && data[1] === 0x4D) {
mimeType = 'image/bmp';
}
else if (data[0] === 0x52 && data[1] === 0x49 && data[2] === 0x46 && data[3] === 0x46 &&
data.length > 11 && data[8] === 0x57 && data[9] === 0x45 && data[10] === 0x42 && data[11] === 0x50) {
mimeType = 'image/webp';
}
}
const blob = new Blob([data], { type: mimeType });
// Use the async Canvas extractor synchronously (this is a limitation of the interface)
// In a real scenario, this should be async, but the WASMModule interface expects sync
return {
width: 0,
height: 0,
format: MediaProcessor.detectFormat(mimeType),
size: data.length,
source: 'canvas',
isValidImage: false,
validationErrors: ['Canvas fallback in WASM context - async extraction not available']
};
},
cleanup() {
// No-op for canvas fallback
}
};
}
}
/**
* Extract metadata from an image blob
*/
static async extractMetadata(blob, options) {
// Auto-initialize if needed
if (!this.initialized) {
await this.initialize();
}
// Check if we should use WASM based on strategy and options
// If useWASM is explicitly true, force WASM usage
// Otherwise, use WASM only if the strategy includes it
const useWASM = options?.useWASM === true ||
(options?.useWASM !== false && this.processingStrategy?.includes('wasm'));
if (!useWASM) {
return this.basicMetadataExtraction(blob);
}
try {
// Apply timeout if specified
const extractPromise = this.extractWithWASM(blob);
if (options?.timeout) {
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), options.timeout));
return await Promise.race([extractPromise, timeoutPromise]);
}
return await extractPromise;
}
catch (error) {
// Fallback to basic extraction on error
// Only log unexpected errors in debug mode
if (process.env.DEBUG && (!(error instanceof Error) || !error.message.includes('WASM module not available'))) {
console.warn('Unexpected error during extraction, using Canvas:', error);
}
return this.basicMetadataExtraction(blob);
}
}
/**
* Extract metadata using WASM
*/
static async extractWithWASM(blob) {
// If WASM module not loaded, try to load it now
if (!this.wasmModule) {
// Try to load WASM on demand
try {
if (!this.loadingPromise) {
this.loadingPromise = this.loadWASM();
}
this.wasmModule = await this.loadingPromise;
}
catch (error) {
// Expected when WASM not available
if (process.env.DEBUG) {
console.warn('WASM not available:', error);
}
throw new Error('WASM module not available');
}
}
// Check if it's actually an image
if (!blob.type.startsWith('image/')) {
return undefined;
}
const arrayBuffer = await blob.arrayBuffer();
const data = new Uint8Array(arrayBuffer);
const metadata = this.wasmModule.extractMetadata(data);
// Ensure format matches blob type and add blob size
if (metadata) {
// Only override format if it's unknown
if (!metadata.format || metadata.format === 'unknown') {
metadata.format = this.detectFormat(blob.type);
}
if (metadata.format === 'png') {
metadata.hasAlpha = true;
}
// Add the actual blob size
metadata.size = blob.size;
}
return metadata;
}
/**
* Basic metadata extraction fallback using Canvas API
*/
static async basicMetadataExtraction(blob) {
try {
// Use the real Canvas metadata extractor
return await CanvasMetadataExtractor.extract(blob);
}
catch (error) {
// This is unexpected - Canvas is the final fallback
if (process.env.DEBUG) {
console.warn('Canvas extraction failed:', error);
}
// Final fallback - return basic info from blob
const format = this.detectFormat(blob.type);
if (format === 'unknown' && !blob.type.startsWith('image/')) {
return undefined;
}
return {
width: 0,
height: 0,
format,
hasAlpha: format === 'png',
size: blob.size,
source: 'canvas',
isValidImage: false,
validationErrors: ['Failed to extract metadata']
};
}
}
/**
* Detect image format from MIME type
*/
static detectFormat(mimeType) {
const typeMap = {
'image/jpeg': 'jpeg',
'image/jpg': 'jpeg',
'image/png': 'png',
'image/webp': 'webp',
'image/gif': 'gif',
'image/bmp': 'bmp'
};
return typeMap[mimeType] || 'unknown';
}
/**
* Check if the MediaProcessor is initialized
*/
static isInitialized() {
return this.initialized;
}
/**
* Get the loaded WASM module (for testing)
*/
static getModule() {
return this.wasmModule;
}
/**
* Get the current processing strategy
*/
static getProcessingStrategy() {
return this.processingStrategy;
}
/**
* Reset the MediaProcessor (for testing)
*/
static reset() {
this.wasmModule = undefined;
this.loadingPromise = undefined;
this.initialized = false;
this.processingStrategy = undefined;
}
}
//# sourceMappingURL=index.js.map