Files
tools/image-scale-cli/cli.ts

136 lines
3.9 KiB
TypeScript

#!/usr/bin/env -S deno run --allow-read --allow-write
import { parseArgs } from "@std/cli/parse-args";
import { scaleImage, type ScaleMode } from "./src/scaler.ts";
import { scanDirectory } from "./src/scanner.ts";
import { processBatch } from "./src/batch.ts";
const HELP = `
scale-image-fixer - Scale images to fixed dimensions
Usage:
deno run cli.ts <input> [options]
deno run cli.ts <input...> [options] # Multiple files
deno run cli.ts <directory> [options] # Directory mode
Arguments:
input Input file path, multiple file paths, or directory path
Options:
-w, --width <number> Output width (required)
-h, --height <number> Output height (required)
-m, --mode <mode> Scale mode: stretch, fit, cover (default: fit)
-o, --output <path> Output path (file or directory)
--help Show this help message
Scale Modes:
stretch Resize to exact dimensions (may distort aspect ratio)
fit Fit within dimensions while preserving aspect ratio
cover Cover dimensions while preserving aspect ratio (crops excess)
Examples:
deno run cli.ts input.png -w 800 -h 600
deno run cli.ts input.jpg -w 400 -h 400 -m cover -o output.jpg
deno run cli.ts ./images -w 200 -h 200 -o ./resized
`;
interface Args {
width?: number;
height?: number;
mode?: string;
output?: string;
help?: boolean;
_: string[];
}
function validateArgs(args: Args): { valid: boolean; error?: string } {
if (args.help) {
return { valid: true };
}
if (args._.length === 0) {
return { valid: false, error: "Input file, files, or directory is required" };
}
if (!args.width || !args.height) {
return { valid: false, error: "Width (-w) and height (-h) are required" };
}
if (args.width <= 0 || args.height <= 0) {
return { valid: false, error: "Width and height must be positive numbers" };
}
const validModes = ["stretch", "fit", "cover"];
if (args.mode && !validModes.includes(args.mode)) {
return { valid: false, error: `Mode must be one of: ${validModes.join(", ")}` };
}
return { valid: true };
}
async function main() {
const args = parseArgs(Deno.args, {
string: ["width", "height", "mode", "output"],
alias: {
w: "width",
h: "height",
m: "mode",
o: "output",
},
default: {
mode: "fit",
},
}) as Args;
const validation = validateArgs(args);
if (!validation.valid) {
console.error(`Error: ${validation.error}`);
console.log("\nUse --help for usage information");
Deno.exit(1);
}
if (args.help) {
console.log(HELP);
Deno.exit(0);
}
const inputs = args._.map(String);
const scaleMode: ScaleMode = (args.mode as ScaleMode) || "fit";
const width = args.width!;
const height = args.height!;
const output = args.output;
try {
// Check if inputs are files or directory
const firstInput = await Deno.stat(inputs[0]);
if (firstInput.isDirectory) {
// Directory mode: scan and batch process
const images = await scanDirectory(inputs[0]);
if (images.length === 0) {
console.log("No supported images found in directory");
Deno.exit(0);
}
await processBatch(images, { width, height, mode: scaleMode, outputDir: output });
} else if (inputs.length > 1) {
// Multiple files mode
await processBatch(inputs, { width, height, mode: scaleMode, outputDir: output });
} else {
// Single file mode
const result = await scaleImage(inputs[0], { width, height, mode: scaleMode, output });
console.log(`Scaled: ${result.input}${result.output}`);
}
} catch (error) {
if (error instanceof Deno.errors.NotFound) {
console.error(`Error: File or directory not found: ${inputs[0]}`);
} else if (error instanceof Error) {
console.error(`Error: ${error.message}`);
} else {
console.error("An unexpected error occurred");
}
Deno.exit(1);
}
}
await main();