import * as path from 'path' import * as pMap from 'p-map' import * as sharp from 'sharp' import { sync as exists } from "@plastichub/fs/exists" import { async as move } from "@plastichub/fs/move" import { sync as dir } from "@plastichub/fs/dir" import { createItem as toNode } from "@plastichub/fs/inspect" import { logger, ERR_PERM_RETRY_DELAY, ERR_PERM_RETRY_MAX, IOptions, IResizeOptions } from '../../../index' import { meta } from './lib' import { targets, targetsNext } from '../..' export const resizeFile = async (source: string, target: string, onNode: (data: sharp.Sharp) => void = () => { }, options: IResizeOptions): Promise => { //const osr_cache = OSR_CACHE() //const ca_options = JSON.parse(JSON.stringify({ ...options, target, skip: null })) //const cached = await get_cached(file, ca_options, MODULE_NAME) const targetOri = '' + target let inPlace = false if (path.resolve(source) === path.resolve(target)) { const parts = path.parse(target) target = path.join(parts.dir, parts.name + '_tmp' + parts.ext) inPlace = true } let image: sharp.Sharp try { image = sharp(source) } catch (e) { logger.error(`Error reading file, ${source}`, e) return } onNode(image) let metaData: any = await meta(source, image) || {} const percent = options.percent const dstParts = path.parse(target) const node = toNode(source, { size: true, mime: true }) if (!exists(dstParts.dir)) { dir(dstParts.dir) } if (options.width && options.minWidth && options.width <= options.minWidth) { logger.error(`Error resizing : options.width <= options.minWidth`) return } if (metaData.width && options.width && options.minWidth) { if (metaData.width <= options.minWidth) { return image } } if (metaData.height && options.height && options.minHeight) { if (metaData.height <= options.minHeight) { return image } } if (options.minSize && node.size && options.minSize >= node.size) { return image } const resizeOptions = { height: options.height, fastShrinkOnLoad: options.fastShrinkOnLoad, withoutEnlargement: options.withoutEnlargement, withoutReduction: options.withoutReduction, fit: options.fit, position: options.position, background: options.background || 'white' } if (percent && metaData.width) { image = image.resize({ width: Math.round(metaData.width * (percent / 100)), ...resizeOptions }) } else if (options.width || options.height) { image = image.resize({ width: options.width, ...resizeOptions }) } else { logger.error(`Error resizing, invalid options for ${source} - no width, height or percent`) return image } if(dstParts.ext.toLowerCase() === '.webp' || dstParts.ext.toLowerCase() === '.png') { image = image.rotate() } if (metaData.width) { await image.withMetadata().toFile(target) } else { try { await image.toFile(target) } catch (e) { logger.error(`Error writing file out, ${source}`, e) return } } if (inPlace) { const timeout = async (retry) => new Promise((resolve) => setTimeout(resolve, ERR_PERM_RETRY_DELAY * retry) ) const moveRetry = async (src, dst, retry = 0) => { if (retry > ERR_PERM_RETRY_MAX) { logger.error(`Error moving file failed, max retries reached ${src}`) return } try { await move(target, targetOri) } catch (e) { if (e.code === 'EPERM') { logger.warn(`Error moving file out, retry ${source}`, e) await timeout(retry) moveRetry(src, dst, retry + 1) } } } await moveRetry(source, targetOri) } logger.debug(`Resized Image ${source} to ${targetOri}`) return image } export const _resize = async (file, targets: string[], onNode: (data: any) => void = () => { }, options: IOptions) => { return pMap(targets, async (target) => { logger.debug(`Resizing ${file} to ${target}`) if (options.dry) { return Promise.resolve() } return resizeFile(file, target, onNode, options); }, { concurrency: 1 }) } export const resize = async (options: IResizeOptions) => { let reports: any = [] logger.setSettings({ minLevel: options.logLevel || 'info' as any }) const onNode = (data: any) => reports.push(data) if (options.srcInfo) { logger.debug(`Convert ${options.srcInfo.FILES.length} files`) return await pMap(options.srcInfo.FILES, async (f) => { const outputs = targetsNext(f, options) logger.debug(`Convert ${f} to `, outputs) return _resize(f, outputs, onNode, options) }, { concurrency: 1 }) } else { logger.error(`Invalid source info`) } return reports }