gif-tools
Version:
A robust, zero-dependency TypeScript library for creating GIF files with support for both static and animated GIFs. Built with modern TypeScript features and designed to work in both Node.js and browser environments.
174 lines (165 loc) • 6.77 kB
JavaScript
/**
* gif-tools CLI entrypoint.
*
* Run `gif-tools --help` for usage. Each command has its own help string in
* the dispatcher below.
*/
import { CliError } from './utils.js';
import { adjustCommand, blurCommand, createCommand, cropCommand, extractCommand, flipCommand, infoCommand, optimizeCommand, reverseCommand, resizeCommand, rotateCommand, speedCommand, } from './commands.js';
const HELP = `gif-tools — terminal toolkit for creating, inspecting, and editing GIFs.
USAGE
gif-tools <command> [options]
COMMANDS
info <input> Show metadata for a GIF
create <type> <output> Create a new GIF (see "create types" below)
resize <input> Resize every frame
crop <input> Crop every frame
rotate <input> Rotate every frame (90 / 180 / 270)
flip <input> Flip horizontally and/or vertically
adjust <input> Adjust brightness, contrast, saturation, hue
blur <input> Box-blur every frame
reverse <input> Reverse frame order
speed <input> Change playback speed
optimize <input> Re-encode with --max-colors and/or --scale
extract <input> Write each frame to its own GIF
CREATE TYPES
solid --width N --height N --color R,G,B
gradient --width N --height N --start R,G,B --end R,G,B [--direction horizontal|vertical|diagonal]
animated-gradient --width N --height N --start R,G,B --end R,G,B
[--animation-type shift|rotate|pulse|wave] [--frames N] [--delay N]
[--loops N] [--intensity F] [--direction ...] [--max-colors N]
checkerboard --width N --height N --color1 R,G,B --color2 R,G,B [--check-size N]
noise --width N --height N [--type white|perlin|simplex] [--scale F] [--seed N]
[--colors "R,G,B;R,G,B"]
fractal --width N --height N [--type mandelbrot|julia|sierpinski] [--iterations N]
[--zoom F] [--center-x F] [--center-y F] [--colors "..."]
geometric --width N --height N [--shape circles|squares|triangles|hexagons]
[--count N] [--size-variation F] [--colors "..."] [--background R,G,B]
spiral --width N --height N [--type archimedean|logarithmic|fibonacci]
[--turns N] [--thickness N] [--colors "..."]
COMMON OPTIONS
-o, --output <file> Output path (required for edit/create commands)
--max-colors N Palette size for re-encoded output (default 256)
-q, --quiet Suppress status output
-h, --help Show this help
-V, --version Print the package version
COLOR FORMATS
R,G,B (e.g. 255,128,0) or #rrggbb (e.g. #ff8000) or #rgb (e.g. #f80).
Color lists use ; as the separator: "255,0,0;0,255,0;0,0,255".
EXAMPLES
gif-tools info pic.gif --dominant 5
gif-tools create solid red.gif --width 64 --height 64 --color #ff0000
gif-tools create animated-gradient sweep.gif -w 200 -h 100 \\
--start #ff0000 --end #0000ff --animation-type shift --frames 24
gif-tools resize in.gif -o small.gif --scale 0.5
gif-tools speed in.gif -o fast.gif --multiplier 2
gif-tools optimize in.gif -o small.gif --max-colors 64 --scale 0.5
gif-tools extract in.gif --output-dir frames/
`;
async function readPackageVersion() {
try {
const fs = await import('fs');
const url = await import('url');
const path = await import('path');
const here = path.dirname(url.fileURLToPath(import.meta.url));
// Compiled location: lib/src/cli/index.js → ../../../package.json
// Source location: src/cli/index.ts → ../../package.json
for (const rel of ['../../../package.json', '../../package.json']) {
try {
const contents = await fs.promises.readFile(path.resolve(here, rel), 'utf8');
const pkg = JSON.parse(contents);
if (typeof pkg.version === 'string')
return pkg.version;
}
catch (_a) {
// try next candidate
}
}
}
catch (_b) {
// fall through
}
return 'unknown';
}
async function main(argv) {
const command = argv[0];
const rest = argv.slice(1);
const quiet = argv.includes('-q') || argv.includes('--quiet');
const ctx = { quiet };
if (!command ||
command === '--help' ||
command === '-h' ||
command === 'help') {
process.stdout.write(HELP);
return 0;
}
if (command === '--version' || command === '-V' || command === 'version') {
const version = await readPackageVersion();
process.stdout.write(`gif-tools ${version}\n`);
return 0;
}
// Top-level --help inside a command should still print the global help.
// Per-command usage hints come from the commands themselves on missing args.
switch (command) {
case 'info':
await infoCommand(rest, ctx);
return 0;
case 'create':
await createCommand(rest, ctx);
return 0;
case 'resize':
await resizeCommand(rest, ctx);
return 0;
case 'crop':
await cropCommand(rest, ctx);
return 0;
case 'rotate':
await rotateCommand(rest, ctx);
return 0;
case 'flip':
await flipCommand(rest, ctx);
return 0;
case 'adjust':
await adjustCommand(rest, ctx);
return 0;
case 'blur':
await blurCommand(rest, ctx);
return 0;
case 'reverse':
await reverseCommand(rest, ctx);
return 0;
case 'speed':
await speedCommand(rest, ctx);
return 0;
case 'optimize':
await optimizeCommand(rest, ctx);
return 0;
case 'extract':
await extractCommand(rest, ctx);
return 0;
default:
process.stderr.write(`Unknown command: ${command}\n\n`);
process.stderr.write(HELP);
return 2;
}
}
main(process.argv.slice(2))
.then(code => {
process.exitCode = code;
})
.catch((err) => {
var _a;
if (err instanceof CliError) {
process.stderr.write(`error: ${err.message}\n`);
}
else if (err instanceof Error) {
process.stderr.write(`error: ${err.message}\n`);
if (process.env.DEBUG)
process.stderr.write(((_a = err.stack) !== null && _a !== void 0 ? _a : '') + '\n');
}
else {
process.stderr.write(`error: ${String(err)}\n`);
}
process.exitCode = 1;
});