/*! * The MIT License (MIT) * * Copyright (c) 2019 Mark van Seventer * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of * the Software, and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ // Strict mode. 'use strict' // Package modules. const yargs = require('yargs') // Local modules. const constants = require('./constants') const pkg = require('../package.json') const queue = require('./queue') // Configure. const IS_TEXT_TERMINAL = process.stdin.isTTY // Options. const _global = 'Global Options' const optimize = 'Optimization Options' const options = { // Global options. // @see https://sharp.pixelplumbing.com/en/stable/api-output/#png compressionLevel: { alias: 'c', desc: 'zlib compression level', defaultDescription: 9, group: _global, nargs: 1, type: 'number' }, // @see https://sharp.pixelplumbing.com/en/stable/api-output/#toformat format: { alias: 'f', choices: [ 'input', ...constants.FORMAT ], default: 'input', desc: 'Force output to a given format', group: _global, nargs: 1 }, // @see https://sharp.pixelplumbing.com/en/stable/api-constructor/ input: { alias: 'i', defaultDescription: 'stdin', demand: IS_TEXT_TERMINAL, desc: 'Path to (an) image file(s)', group: _global, implies: 'output', normalize: true, type: 'array' }, // @see https://sharp.pixelplumbing.com/en/stable/api-input/#limitinputpixels limitInputPixels: { alias: 'l', defaultDescription: 0x3FFF * 0x3FFF, desc: 'Do not process input images where the number of pixels (width x height) exceeds this limit', group: _global, nargs: 1, type: 'number' }, // @see https://sharp.pixelplumbing.com/en/stable/api-output/ output: { alias: 'o', defaultDescription: 'stdout', demand: IS_TEXT_TERMINAL, desc: 'Directory or URI template to write the image files to', group: _global, nargs: 1, normalize: true, type: 'string' }, // @see https://sharp.pixelplumbing.com/en/stable/api-output/#jpeg // @see https://sharp.pixelplumbing.com/en/stable/api-output/#png progressive: { alias: 'p', desc: 'Use progressive (interlace) scan', group: _global, type: 'boolean' }, // @see https://sharp.pixelplumbing.com/en/stable/api-output/#jpeg // @see https://sharp.pixelplumbing.com/en/stable/api-output/#tiff // @see https://sharp.pixelplumbing.com/en/stable/api-output/#webp quality: { alias: 'q', desc: 'Quality', defaultDescription: '80', group: _global, nargs: 1, type: 'number' }, // @see https://sharp.pixelplumbing.com/en/stable/api-output/#withmetadata withMetadata: { alias: 'm', desc: 'Include all metadata (EXIF, XMP, IPTC) from the input image in the output image', global: true, group: _global, type: 'boolean' }, // Optimization options. // @see https://sharp.pixelplumbing.com/en/stable/api-output/#png adaptiveFiltering: { desc: 'Use adaptive row filtering', group: optimize, type: 'boolean' }, // @see https://sharp.pixelplumbing.com/en/stable/api-output/#webp alphaQuality: { desc: 'Quality of alpha layer', defaultDescription: '80', group: optimize, nargs: 1, type: 'number' }, // @see https://sharp.pixelplumbing.com/en/stable/api-output/#jpeg chromaSubsampling: { desc: 'Set to "4:4:4" to prevent chroma subsampling when quality <= 90', defaultDescription: '4:2:0', group: optimize, nargs: 1, type: 'string' }, // @see https://sharp.dimens.io/en/stable/api-output/#png colors: { alias: 'colours', defaultDescription: 256, desc: 'Maximum number of palette entries', group: optimize, nargs: 1, type: 'number' }, // @see https://sharp.pixelplumbing.com/en/stable/api-output/#tiff compression: { choices: constants.TIFF_COMPRESSION, default: 'jpeg', desc: 'Compression options', group: optimize, nargs: 1, type: 'string' }, // @see https://sharp.dimens.io/en/stable/api-output/#png dither: { desc: 'Level of Floyd-Steinberg error diffusion', defaultDescription: '1.0', group: optimize, nargs: 1, type: 'number' }, // @see https://sharp.pixelplumbing.com/en/stable/api-output/#webp lossless: { desc: 'Use lossless compression mode', group: optimize, type: 'boolean' }, // @see https://sharp.pixelplumbing.com/en/stable/api-output/#webp nearLossless: { desc: 'use near_lossless compression mode', group: optimize, type: 'boolean' }, // @see https://sharp.pixelplumbing.com/en/stable/api-output/#jpeg optimise: { alias: 'optimize', desc: 'Apply optimiseScans, overshootDeringing, and trellisQuantisation', group: optimize, type: 'boolean' }, // @see https://sharp.pixelplumbing.com/en/stable/api-output/#jpeg optimiseCoding: { alias: 'optimizeCoding', default: true, desc: 'Optimise Huffman coding tables', group: optimize, type: 'boolean' }, // @see https://sharp.pixelplumbing.com/en/stable/api-output/#jpeg optimiseScans: { alias: 'optimizeScans', desc: 'Optimise progressive scans', group: optimize, implies: 'progressive', type: 'boolean' }, // @see https://sharp.pixelplumbing.com/en/stable/api-output/#jpeg overshootDeringing: { desc: 'Apply overshoot deringing', group: optimize, type: 'boolean' }, // @see https://sharp.dimens.io/en/stable/api-output/#png palette: { desc: 'Quantise to a palette-based image with alpha transparency support', group: optimize, type: 'boolean' }, // @see https://sharp.pixelplumbing.com/en/stable/api-output/#tiff predictor: { choices: constants.TIFF_PREDICTOR, default: 'horizontal', desc: 'Compression predictor', group: optimize, nargs: 1, type: 'string' }, // @see https://sharp.pixelplumbing.com/en/stable/api-output/#tiff pyramid: { desc: 'Write an image pyramid', group: optimize, type: 'boolean' }, // @see https://sharp.pixelplumbing.com/en/stable/api-output/#jpeg quantisationTable: { alias: 'quantizationTable', defaultDescription: '0', desc: 'Quantization table to use', group: optimize, nargs: 1, type: 'number' }, // @see https://sharp.pixelplumbing.com/en/stable/api-input/#sequentialread sequentialRead: { desc: 'An advanced setting that switches the libvips access method to VIPS_ACCESS_SEQUENTIAL', group: optimize, type: 'boolean' }, // @see https://sharp.pixelplumbing.com/en/stable/api-output/#tiff squash: { desc: 'Squash 8-bit images down to 1 bit', group: optimize, type: 'boolean' }, // @see https://sharp.pixelplumbing.com/en/stable/api-output/#tiff tileHeight: { desc: 'Vertical tile size', group: optimize, type: 'number' }, // @see https://sharp.pixelplumbing.com/en/stable/api-output/#tiff tileWidth: { desc: 'Horizontal tile size', group: optimize, type: 'number' }, // @see https://sharp.pixelplumbing.com/en/stable/api-output/#jpeg trellisQuantisation: { desc: 'Apply trellis quantisation', group: optimize, type: 'boolean' }, // @see https://sharp.pixelplumbing.com/en/stable/api-output/#tiff xres: { defaultDescription: '1.0', desc: 'Horizontal resolution', group: optimize, type: 'number' }, // @see https://sharp.pixelplumbing.com/en/stable/api-output/#tiff yres: { defaultDescription: '1.0', desc: 'Vertical resolution', group: optimize, type: 'number' } } // Configure. const cli = yargs .strict() .usage('$0 <options> [command..]') .options(options) .example('$0 -i ./input.jpg -o ./out resize 300 200', 'out/input.jpg will be a 300 pixels wide and 200 pixels high image containing a scaled and cropped version of input.jpg') .example('$0 -i ./input.jpg -o ./out -mq90 rotate 180 -- resize 300 -- flatten "#ff6600" -- overlayWith ./overlay.png --gravity southeast -- sharpen', 'out/input.jpg will be an upside down, 300px wide, alpha channel flattened onto orange background, composited with overlay.png with SE gravity, sharpened, with metadata, 90% quality version of input.jpg') .epilog('For more information on available options, please visit https://sharp.pixelplumbing.com/') .showHelpOnFail(false) // Built-in options. .help().alias('help', 'h') .version(pkg.version).alias('version', 'v') .group([ 'help', 'version' ], 'Misc. Options') // Commands. // Avoid `yargs.commandDir()` as it uses insertion order, not alphabetical. .command(require('../cmd/channel-manipulation/bandbool')) .command(require('../cmd/operations/blur')) .command(require('../cmd/operations/boolean')) .command(require('../cmd/compositing/composite')) .command(require('../cmd/operations/convolve')) .command(require('../cmd/channel-manipulation/ensure-alpha')) .command(require('../cmd/resizing/extend')) .command(require('../cmd/resizing/extract')) .command(require('../cmd/channel-manipulation/extract')) .command(require('../cmd/operations/flatten')) .command(require('../cmd/operations/flip')) .command(require('../cmd/operations/flop')) .command(require('../cmd/operations/gamma')) .command(require('../cmd/colour-manipulation/greyscale')) .command(require('../cmd/channel-manipulation/join')) .command(require('../cmd/operations/linear')) .command(require('../cmd/operations/median')) .command(require('../cmd/operations/modulate')) .command(require('../cmd/operations/negate')) .command(require('../cmd/operations/normalise')) .command(require('../cmd/compositing/overlay-with')) .command(require('../cmd/operations/recomb')) .command(require('../cmd/channel-manipulation/remove-alpha')) .command(require('../cmd/resizing/resize')) .command(require('../cmd/operations/rotate')) .command(require('../cmd/operations/sharpen')) .command(require('../cmd/operations/threshold')) .command(require('../cmd/colour-manipulation/tint')) .command(require('../cmd/output')) .command(require('../cmd/colour-manipulation/tocolourspace')) .command(require('../cmd/resizing/trim')) // Override `cli.parse` to handle global options. const originalParse = cli.parse cli.parse = (argv, context, callback) => { // Context is optional. if (typeof context === 'function') { callback = context context = { } } // Parse and return the arguments. return originalParse(argv, context, (err, args, output) => { // Handle arguments. // NOTE Use queue.unshift to apply global options first. if (args !== null) { // Require at least one input file. // NOTE: check here b/c https://github.com/yargs/yargs/issues/403 if (args.input && args.input.length === 0) { err = new Error('Not enough arguments following: i, input') } // Global Options. // @see https://sharp.pixelplumbing.com/en/stable/api-output/#toformat if (args.format !== options.format.default) { queue.unshift([ 'format', (sharp) => sharp.toFormat(args.format) ]) } // @see https://sharp.pixelplumbing.com/en/stable/api-output/#jpeg if ( args.chromaSubsampling || args.optimise || args.optimiseCoding !== true || args.optimiseScans || args.overshootDeringing || args.progressive || args.quantisationTable || args.quality || args.trellisQuantisation ) { queue.unshift([ 'jpeg', (sharp) => { return sharp.jpeg({ chromaSubsampling: args.chromaSubsampling, force: false, optimiseCoding: args.optimiseCoding, optimiseScans: args.optimise || args.optimiseScans, overshootDeringing: args.optimise || args.overshootDeringing, progressive: args.progressive, quality: args.quality, quantisationTable: args.quantisationTable, trellisQuantisation: args.optimise || args.trellisQuantisation }) }]) } // @see https://sharp.pixelplumbing.com/en/stable/api-input/#limitinputpixels if (undefined !== args.limitInputPixels) { queue.unshift([ 'limitInputPixels', (sharp) => sharp.limitInputPixels(args.limitInputPixels) ]) } // @see https://sharp.pixelplumbing.com/en/stable/api-output/#png if (args.adaptiveFiltering || args.colors || args.compressionLevel || args.dither || args.palette || args.progressive) { queue.unshift([ 'png', (sharp) => { return sharp.png({ adaptiveFiltering: args.adaptiveFiltering, colors: args.colors, compressionLevel: args.compressionLevel, dither: args.dither, force: false, palette: args.palette, progressive: args.progressive }) }]) } // @see https://sharp.pixelplumbing.com/en/stable/api-input/#sequentialread if (args.sequentialRead) { queue.unshift([ 'sequentialRead', (sharp) => sharp.sequentialRead() ]) } // @see https://sharp.pixelplumbing.com/en/stable/api-output/#tiff if (args.compression !== options.compression.default || args.predictor !== options.predictor.default || args.pyramid || args.quality || args.squash || args.tileHeight || args.tileWidth || args.xres || args.yres) { queue.unshift([ 'tiff', (sharp) => { return sharp.tiff({ compression: args.compression, force: false, predictor: args.predictor, pyramid: args.pyramid, quality: args.quality, squash: args.squash, tile: args.tileWidth !== undefined || args.tileHeight !== undefined, tileHeight: args.tileHeight || args.tileWidth, tileWidth: args.tileWidth || args.tileHeight, xres: args.xres, yres: args.yres }) }]) } // @see https://sharp.pixelplumbing.com/en/stable/api-output/#webp if (args.alphaQuality || args.quality || args.lossless || args.nearLossless) { queue.unshift([ 'webp', (sharp) => { return sharp.webp({ alphaQuality: args.alphaQuality, force: false, lossless: args.lossless, nearLossless: args.nearLossless, quality: args.quality }) }]) } // @see https://sharp.pixelplumbing.com/en/stable/api-output/#withmetadata if (args.withMetadata) { queue.unshift([ 'withMetadata', (sharp) => sharp.withMetadata() ]) } } // Invoke original. return callback(err, args, output) }) } // Exports. module.exports = cli