image-content-crop
v1.1.2
Published
AI-powered CLI to batch-process images: crop to content (remove margins), with optional Gemini post-processing. Configurable via YAML presets or CLI options. Usable with npx.
Readme
Image Content Crop
CLI to crop images to their content area by removing margins — and optionally post-process the cropped result with Google Gemini (image generation). Presets are defined in a YAML config file; margins and Gemini prompts can also be set via command-line options.
Use it for any workflow that needs consistent image cropping (e.g. stripping headers, footers, or page numbers) with an optional AI-powered editing pass.
Requirements
- Node.js 20+
- Build once before using:
npm run build
Installation
Local (clone or download):
npm install
npm run buildRun with npx (no install):
npx image-content-crop --help
npx image-content-crop batch -s ./my-images -d ./cropped -p aIf you publish this package to npm, anyone can run it with:
npx image-content-crop <command> [options]Configuration (YAML)
Config file resolution
Config files are resolved in the following order (first existing file wins). At each location, a local override file is checked first:
- Current directory:
./crop-config.local.yaml→./crop-config.yaml - User home:
~/.config/image-content-crop/crop-config.local.yaml→~/.config/image-content-crop/crop-config.yaml - Package default: the
crop-config.yamlshipped with the package
Use --config <path> to force a specific file.
The crop-config.local.yaml file is gitignored by default — use it for personal overrides (custom prompts, project-specific margins) without affecting the shared config.
Preset format
Presets define margin ratios (0–1) or pixel values, and optional Gemini post-processing settings.
# Global Gemini settings (optional).
# Requires GEMINI_API_KEY env var. Model can be overridden per preset.
gemini:
model: gemini-2.5-flash-image
presets:
a:
marginLeft: 0.10
marginRight: 0.10
marginBottom: 0.14
marginTop: 0.12
# Per-preset Gemini processing (optional).
# Sends cropped image + prompt to Gemini for editing.
# Use --no-gemini to skip at runtime.
# gemini:
# prompt: "Recreate this image with a fresh clean look"
# model: gemini-2.5-flash-image # optional, overrides global
b:
marginLeft: 0.10
marginRight: 0.10
marginBottom: 0.10
marginTop: 0.12- marginLeft / marginRight / marginTop / marginBottom: ratio 0–1 (e.g. 0.10 = 10%), or pixels if you set
marginsInPixels: truein the preset. - gemini.prompt (per preset): when present, the cropped image is sent to Gemini with this prompt. Omit to skip Gemini for the preset.
- gemini.model (per preset, optional): overrides the global model for this preset.
- Preset names are arbitrary (e.g.
a,b,sample); use--preset <name>to apply one.
CLI usage
Single file
With single subcommand:
npx image-content-crop single --source path/to/image.png --dest path/to/out.png
npx image-content-crop single -s input.png -d output.png
# With a preset from config
npx image-content-crop single -s input.png -d output.png -p a
# With custom config file
npx image-content-crop single -s input.png -d output.png -c ./my-crop-config.yaml -p my-presetShort form (no subcommand):
npx image-content-crop --src input.png --out output.pngBatch (whole directory)
Default directories are in (source) and out (destination).
# Default: reads from "in", writes to "out"
npx image-content-crop batch
# Presets A and B (e.g. in/A → out/A, in/B → out/B)
npx image-content-crop batch -s in/A -d out/A -p a
npx image-content-crop batch -s in/B -d out/B -p b
# Custom dirs and preset
npx image-content-crop batch --source-dir ./images --dest-dir ./cropped --preset a
npx image-content-crop batch -s ./images -d ./cropped -p b -c ./crop-config.yamlnpm scripts (from project root; they use sample-data by default):
npm run crop:a— batch cropsample-data/in/A→sample-data/out/Awith preset anpm run crop:b— batch cropsample-data/in/B→sample-data/out/Bwith preset bnpm run crop:sample— batch cropsample-data/in/sample→sample-data/out/samplewith preset samplenpm run crop— run the CLI (help and ad-hoc commands)
Margin options (override preset or use without preset)
You can override any preset margin with:
-l, --margin-left <n>— left margin (0–1 ratio, or pixels if--margins-in-pixels)-t, --margin-top <n>— top margin-r, --margin-right <n>— right margin (default 0.06)-b, --margin-bottom <n>— bottom margin (default 0.14)--margins-in-pixels— treat all margins as pixel values
Example (more crop on right and bottom):
npx image-content-crop single -s page.png -d out.png -r 0.08 -b 0.16Gemini integration (optional)
After cropping, images can be sent to Google Gemini for AI-powered post-processing (e.g. de-identifying content, refreshing visuals). This step is optional and disabled by default.
Setup
Set the
GEMINI_API_KEYenvironment variable (or add it to a.envfile at the project root — the CLI loadsdotenvautomatically).Add a
geminiblock to your preset in the config file:
gemini:
model: gemini-2.5-flash-image # global default model
presets:
my-preset:
marginLeft: 0.10
marginRight: 0.10
marginBottom: 0.14
marginTop: 0.12
gemini:
prompt: "Replace photos with different generic scenes; keep all text unchanged."
model: gemini-2.5-flash-image # optional, overrides globalCLI options
These options are available on both single and batch subcommands:
| Option | Description |
|---|---|
| --no-gemini | Skip Gemini processing even if the preset configures it |
| --gemini-prompt <text> | Override the Gemini prompt from the preset |
| --gemini-interval-ms <n> | Batch only: minimum ms between Gemini API calls (default 2000) |
Examples:
# Crop + Gemini with preset
npx image-content-crop single -s input.png -d output.png -p sample
# Crop only (skip Gemini even if preset has it)
npx image-content-crop batch -s in/A -d out/A -p a --no-gemini
# Override prompt from command line
npx image-content-crop single -s input.png -d output.png --gemini-prompt "Make colors brighter"
# Batch with faster pacing (500ms between API calls)
npx image-content-crop batch -s in/A -d out/A -p a --gemini-interval-ms 500Retry and timeout behaviour
Gemini API calls are retried automatically with exponential backoff and jitter on:
- HTTP 429 (rate limit)
- HTTP 5xx (server errors)
- Transient network errors (
ECONNRESET,ETIMEDOUT,EAI_AGAIN)
Environment variables
| Variable | Default | Description |
|---|---|---|
| GEMINI_API_KEY | (required) | Google Gemini API key |
| GEMINI_HTTP_TIMEOUT_MS | 300000 (5 min) | HTTP timeout per Gemini request |
| GEMINI_RETRY_MAX_ATTEMPTS | 5 | Maximum number of retry attempts |
| GEMINI_RETRY_INITIAL_MS | 1000 | Initial backoff delay (ms) |
| GEMINI_RETRY_MAX_MS | 60000 | Maximum backoff delay cap (ms) |
| GEMINI_MIN_INTERVAL_MS | 2000 | Minimum delay between batch Gemini calls (also settable via --gemini-interval-ms) |
Expected layout
- Source directory: any folder (default
infor batch). - Output directory: any folder (default
outfor batch). - Supported formats:
.png,.jpg,.jpeg,.webp,.gif.
Example with two presets (A and B): the sample-data folder uses sample-data/in/A, sample-data/in/B and sample-data/out/A, sample-data/out/B. From the project root, npm run crop:a and npm run crop:b process that folder. For your own data, use in/ and out/ at the project root (or pass -s / -d).
Development
- Lint:
npm run lint(Biome) - Format:
npm run formatornpm run check(Biome format + lint with auto-fix) - Unit tests:
npm run test(watch) ornpm run test:run(single run). Test files sit next to the source (e.g.src/crop.test.ts,src/gemini.test.ts). - Typecheck:
npm run typecheck
Publishing to npm (GitHub-hosted)
To publish this CLI to the npm registry with the repo on GitHub:
GitHub
- The project is configured for github.com/yortyrh/image-content-crop. To use another repo, update
repository,homepage, andbugs.urlinpackage.json.
- The project is configured for github.com/yortyrh/image-content-crop. To use another repo, update
npm
- Create an account at npmjs.com if needed.
- Log in:
npm login. - From the project root, run:
npm run buildnpm publish
- If the package name
image-content-cropis taken, use a scoped name (e.g.@your-username/image-content-crop) and publish withnpm publish --access public.
After publishing
- Anyone can run:
npx image-content-crop --helpornpx image-content-crop batch -s ./in -d ./out -p a. prepublishOnlyrunsnpm run buildbefore each publish so the builtdist/is up to date.
- Anyone can run:
Deploying a new version (after pushing changes)
Releases are published to npm automatically via GitHub Actions when you push a version tag.
One-time setup (if not done yet) In the repo: Settings → Secrets and variables → Actions. Add a secret
NPM_TOKENwith an npm access token that has Publish permission (automation or classic token).For each release
- Update the
versionfield inpackage.json(e.g.1.1.0). The tag andpackage.jsonversion must match. - Commit and push:
git add package.json git commit -m "chore: release v1.1.0" git push origin main - Create and push a tag (use the same version as in
package.json):git tag v1.1.0 git push origin v1.1.0 - The Publish to npm workflow will run: it typechecks, builds, and publishes to npm. Check the Actions tab on GitHub for status.
- Update the
