Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## 6.0.0

- Fixed format-specific output options being applied to all formats ([#92](https://github.com/vseventer/sharp-cli/issues/92)).
- Replaced `jpg` with [`jpeg`](https://sharp.pixelplumbing.com/api-output#jpeg) as an output format option.

## 5.3.0 (August 13, 2026)

> Last release compatible with Node.js 18.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ Output Options
--bigtiff Use BigTIFF variant [boolean]
-c, --compressionLevel zlib compression level [number] [default: 6]
-f, --format Force output to a given format
[choices: "avif", "gif", "heif", "jpeg", "jpg", "png", "tiff", "webp"] [default: input]
[choices: "avif", "gif", "heif", "jpeg", "png", "tiff", "webp"] [default: input]
--keepDuplicateFrames Keep duplicate frames in the output instead of combining them
[boolean]
-m, --metadata, --withMetadata Include all metadata (EXIF, XMP, IPTC) from the input image in the
Expand Down
21 changes: 14 additions & 7 deletions lib/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -739,7 +739,8 @@ cli.parse = function recursiveParse(args, context = {}) {
) {
queue.unshift([
"heif",
(sharp) => {
(sharp, { format = "heif" }) => {
if (format !== "heif") return sharp;
return sharp.heif({
bitdepth: argv.hbitdepth,
compression: argv.hcompression,
Expand All @@ -761,7 +762,8 @@ cli.parse = function recursiveParse(args, context = {}) {
) {
queue.unshift([
"avif",
(sharp) => {
(sharp, { format = "avif" }) => {
if (format !== "avif") return sharp;
return sharp.avif({
chromaSubsampling: argv.chromaSubsampling,
effort: argv.effort,
Expand All @@ -788,7 +790,8 @@ cli.parse = function recursiveParse(args, context = {}) {
) {
queue.unshift([
"gif",
(sharp) => {
(sharp, { format = "gif" }) => {
if (format !== "gif") return sharp;
return sharp.gif({
colors: argv.colors,
force: false,
Expand Down Expand Up @@ -821,7 +824,8 @@ cli.parse = function recursiveParse(args, context = {}) {
) {
queue.unshift([
"jpeg",
(sharp) => {
(sharp, { format = "jpeg" }) => {
if (format !== "jpeg") return sharp;
return sharp.jpeg({
chromaSubsampling: argv.chromaSubsampling,
force: false,
Expand Down Expand Up @@ -850,7 +854,8 @@ cli.parse = function recursiveParse(args, context = {}) {
) {
queue.unshift([
"png",
(sharp) => {
(sharp, { format = "png" }) => {
if (format !== "png") return sharp;
return sharp.png({
adaptiveFiltering: argv.adaptiveFiltering,
colors: argv.colors,
Expand Down Expand Up @@ -883,7 +888,8 @@ cli.parse = function recursiveParse(args, context = {}) {
) {
queue.unshift([
"tiff",
(sharp) => {
(sharp, { format = "tiff" }) => {
if (format !== "tiff") return sharp;
return sharp.tiff({
background: argv.tileBackground,
bigtiff: argv.bigtiff,
Expand Down Expand Up @@ -920,7 +926,8 @@ cli.parse = function recursiveParse(args, context = {}) {
) {
queue.unshift([
"webp",
(sharp) => {
(sharp, { format = "webp" }) => {
if (format !== "webp") return sharp;
return sharp.webp({
alphaQuality: argv.alphaQuality,
effort: argv.effort,
Expand Down
2 changes: 1 addition & 1 deletion lib/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export default {
EXTEND_WITH: ["background", "copy", "repeat", "mirror"],
FAIL_ON: ["none", "truncated", "error", "warning"],
FIT: Object.keys(sharp.fit),
FORMAT: ["avif", "gif", "heif", "jpeg", "jpg", "png", "tiff", "webp"],
FORMAT: ["avif", "gif", "heif", "jpeg", "png", "tiff", "webp"],
GRAVITY: Object.keys(sharp.gravity),
HEIF_COMPRESSION: ["hevc", "av1"],
INTERPOLATORS: Object.keys(sharp.interpolators),
Expand Down
16 changes: 12 additions & 4 deletions lib/convert.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,16 @@ const EXTENSIONS = {
webp: ".webp",
};

// Helpers.
const getFormat = (format, src) => {
if (format) return format;
return path.extname(src).slice(1);
};

// Exports.
export default {
// Convert a list of files.
files: (input, output, options) => {
files: (input, output, options, format) => {
// Resolve files.
const files = input.reduce((list, input) => {
return list.concat(globSync(input, { absolute: true }));
Expand All @@ -63,7 +69,9 @@ export default {
const isBatch = files.length > 1;
const promises = files.map((src) => {
// Create pipeline.
const transformer = queue.drain(sharp(options));
const transformer = queue.drain(sharp(options), {
format: getFormat(format, src),
});

// Process output as a template.
const parts = path.parse(src);
Expand Down Expand Up @@ -99,9 +107,9 @@ export default {
},

// Convert a stream.
stream: (inStream, outStream, options) => {
stream: (inStream, outStream, options, format) => {
// Create pipeline.
const transformer = queue.drain(sharp(options));
const transformer = queue.drain(sharp(options), { format });

// Gather return value.
const info = {};
Expand Down
9 changes: 7 additions & 2 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,14 @@ export default (args, options = {}) => {
.then((argv) => {
const options = pick(argv, cli.inputOptions);
if (argv.input) {
return convert.files(argv.input, argv.output, options);
return convert.files(argv.input, argv.output, options, argv.format);
}
return convert.stream(process.stdin, process.stdout, options);
return convert.stream(
process.stdin,
process.stdout,
options,
argv.format,
);
})
.then((output) => {
const info = Array.isArray(output) ? output : [output];
Expand Down
4 changes: 2 additions & 2 deletions lib/queue.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ const queue = [];
Object.defineProperties(queue, {
// Add drain handler.
drain: {
value: (initialValue) =>
queue.reduce((acc, [, cb]) => cb(acc), initialValue),
value: (initialValue, config = {}) =>
queue.reduce((acc, [, cb]) => cb(acc, config), initialValue),
},

// Add pipeline getter.
Expand Down
6 changes: 6 additions & 0 deletions test/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,12 @@ describe(`${pkg.name} <options> [command..]`, () => {
const pipeline = queue.drain(sharp());
sinon.assert.calledWithMatch(pipeline.avif, { effort });
});

it("must only apply the option to the selected format", () => {
const pipeline = queue.drain(sharp(), { format: "png" });
sinon.assert.calledWithMatch(pipeline.png, { effort });
sinon.assert.notCalled(pipeline.webp);
});
});

describe("--failOn", () => {
Expand Down
6 changes: 6 additions & 0 deletions test/convert.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import fs from "fs-extra";
import tempy from "tempy";

// Local modules.
import cli from "../lib/cli.js";
import convert from "../lib/convert.js";
import queue from "../lib/queue.js";
import tile from "../cmd/output.js";
Expand Down Expand Up @@ -75,6 +76,11 @@ describe("convert", () => {
expect(info.path).to.contain(".avif");
});
});
it("must select format options based on the input extension", async () => {
await cli.parse(["--effort", 7, "-i", input, "-o", dest]);
const [info] = await convert.files([input], dest, options);
expect(info).to.have.property("format", "jpeg");
});
it("must convert a file and output to an existing directory", () => {
// Negative test for directory that does not exist.
const rand = "" + Math.random();
Expand Down
Loading