Docs.

Everything squish can do, with the exact commands to do it. Compresses images, video, and audio; minifies JS, TS, CSS, HTML, and JSON — all local, non-destructive by default.

On this page

Install

Homebrew (macOS — recommended)

brew install mikedre/tap/squish

Installs a prebuilt binary plus every system dependency (ffmpeg, gifsicle, libheif, dav1d). Nothing else to do.

Prebuilt binaries

Each release ships binaries for macOS (arm64/x64) and Linux (x64/arm64) on the releases page. Unpack and put squish on your PATH. Linux binaries need libheif (≥ 1.18) and dav1d (≥ 1.3) present at runtime, plus the subprocess dependencies below for full format coverage.

Install via cargo

cargo install squish-media-cli --locked

Compiles squish from crates.io (stable Rust 1.95+) and places the binary in ~/.cargo/bin. --locked installs the exact dependency versions squish was released with; without it cargo may re-resolve transitive crates to newer versions that fail to compile. You still need the system dependencies for full format support.

Build from source

# installs system deps via Homebrew (macOS) or apt (Linux)
./scripts/setup.sh
cargo install --path crates/squish-cli

System dependencies

GIF and HEIC support require external libraries. Install them for full format coverage:

DependencyNeeded formacOSLinux
gifsicleGIF compressionbrew install gifsicleapt install gifsicle
libheif + x265HEIC/HEIFbrew install libheif x265apt install libheif-dev libx265-dev
dav1dAVIF decodingbrew install dav1dapt install libdav1d-dev
ffmpegVideo & audiobrew install ffmpegapt install ffmpeg

If a dependency is missing when you need it, squish tells you exactly what to install.

Check your setup

See which formats work on this machine and whether the optional tools are installed:

squish doctor

Images and code minification work out of the box. Video and audio need ffmpeg, and GIF needs gifsicle; doctor shows what's present (with versions) and how to install anything missing. It always exits 0.

Images

Common ways to run squish against images:

# Single file
squish dog.png
# → dog_squished.png

# Whole folder, recursively
squish ./assets/ -r

# Convert format while compressing
squish photos/ -r --format webp --quality 75

# Preserve every bit (lossless)
squish logo.svg --lossless

# Resize while compressing (never upscales)
squish photos/ -r --max-width 2000

# Fit within a box
squish hero.jpg --max-width 1920 --max-height 1080

# Crop to a preset aspect ratio (largest centred fit), then compress
squish hero.jpg --crop 16:9

# Square-crop a whole folder, anchored to the top of each image
squish avatars/ -r --crop 1:1 --gravity north

# Crop an exact pixel region: WxH+X+Y
squish scan.png --crop 800x600+120+40

# Pick the crop region by eye, in your browser
squish hero.jpg --select

# Compress to a size budget (highest quality that fits)
squish hero.jpg --target-size 500k

# Compress as hard as possible with no visible loss (perceptual auto-quality)
squish photo.jpg --quality auto

# Web-optimize: resize to 1920px, convert to WebP, visually-lossless quality (H.264 for video)
squish ./assets -r --preset web

# Preview without writing
squish ./big-folder/ -r --dry-run

# Keep watching a folder, squishing files as they land
squish ./assets/ -r --watch

Converting SVG to a raster format

# Render at a pixel width — height follows the SVG's own aspect ratio
squish logo.svg --format png --width 512

# Render at a pixel height instead
squish icon.svg --format webp --height 64

# A whole folder of vector assets, rasterised in one pass
squish assets/ --format png --width 256

A vector has no pixel size of its own, so converting an SVG to PNG, JPEG, WebP, AVIF, GIF, HEIC, or TIFF needs --width or --height to say how big to render it — leaving both off is a per-file error, and the rest of a directory run still proceeds. Give both and squish fits the render inside that box at the SVG's own aspect ratio, without stretching it; unlike --max-width/--max-height, these flags upscale, since there's no native resolution to scale down from. An SVG with no viewBox and no absolute width/height has nothing to scale from either way, so that's also an error. Text renders with whatever fonts are installed on the machine, and squish warns you by name if one the file asks for isn't there.

--crop and --select operate on the rendered pixels, so --width sizes the artboard first — cropping a 512-wide render still yields something smaller than 512. --quality auto and --target-size work the same way they do for any other raster output.

squish's never-grow guarantee — never writing an output larger than the input — does not apply to this conversion: rasterising is a change of representation, not a re-encode, so a compact vector routinely comes out larger once it's pixels. Raster to SVG remains unsupported.

Picking a crop by eye

--select opens a local page in your browser — nothing is uploaded, and the server is bound to 127.0.0.1 with a single-use token, shutting down the moment you confirm or cancel. Drag out a region and the readout shows its exact size in source pixels, its offset, its aspect ratio, and the real compressed size it will produce. Ratio presets, eight resize handles, arrow-key nudging (1 px, or 10 px with shift), and zoom/pan are all there.

It takes exactly one image. The rect is echoed when the run finishes:

$ squish hero.jpg --select
crop: 1440x810+240+120
hero_squished.jpg  4.4 MB → 812 KB (-82%)

Reuse that spec to repeat the crop non-interactively — handy in scripts:

squish hero.jpg --crop 1440x810+240+120

Pair it with --dry-run to pick a region without writing anything, and --crop 1440x810+240+120 --select to reopen a previous crop and adjust it.

Video

Common ways to run squish against video:

# Compress a video (defaults to H.265)
squish video.mp4
# → video_squished.mp4

# Use H.264 instead
squish video.mp4 --codec h264

# Fast mode — optimise without re-encoding
squish video.mp4 --fast

# Fit a clip under an upload limit (bitrate computed from duration)
squish clip.mp4 --target-size 8M

# Mixed batch — images and videos together
squish ./media/ -r
# → Squished 8 files (5 images, 3 videos) · 120.3 MB → 34.1 MB (-71.7%)

# Convert a .mov to .mp4 (re-encodes with the container default codec)
squish trailer.mov --format mp4
# → trailer_squished.mp4

Audio

Common ways to run squish against audio:

# Single file — re-encode at the same codec with sensible quality
squish track.mp3

# Convert a lossless file to Opus (~50% size reduction)
squish --codec opus song.flac

# Pick a specific bitrate
squish --bitrate 192k podcast.mp3

# Strip ID3 tags and album art
squish --strip-tags album/*.mp3

# Fit a podcast under a size budget (bitrate computed from duration)
squish episode.mp3 --target-size 25M

# Convert lossless to a specific container/codec
squish song.flac --format opus
# → song_squished.opus

Code

Common ways to run squish against code:

# Minify everything in dist/ recursively
squish dist/ -r
# → app.js → app.min.js, style.css → style.min.css, …

# Safe mode — whitespace-only, no identifier mangling
squish --safe app.js

# Emit a source map alongside the minified output
squish --source-map app.js style.css

Known limitations: IE conditional comments (<!--[if IE]>...<![endif]-->) are stripped along with regular comments. Pass --source-map if you need to preserve comments in JS/CSS for debugging.

Usage report

# How much have I saved this month + all-time?
squish --stats

# Skip recording this run (also: SQUISH_NO_STATS=1)
squish photos/ -r --no-stats

Formats

Images

Supported as input and output: PNG, JPEG, WebP, AVIF, SVG, GIF, HEIC, TIFF.

FormatLibrary
PNGoxipng + imagequant
JPEGmozjpeg (progressive, optimised Huffman)
WebPlibwebp (static); animated WebP copies through unchanged
AVIFravif (encode) + dav1d (decode)
SVGoxvg_optimiser (SVGO-equivalent: comments, default attrs, relative path coords) as input/output; resvg renders it to any raster format below given --width/--height
GIF (static + animated)gifsicle -O3
HEIClibheif-rs
TIFFinput only — defaults to re-encoding as JPEG; use --format tiff to keep TIFF output

JPEG has no alpha channel, so converting a transparent image to it — png → jpg, or any SVG rendered to JPEG — composites the transparency onto a white background rather than discarding it. There's deliberately no --background flag to change the colour.

Video

Supported containers: MP4, WebM, MOV, AVI, MKV, FLV, DV (→ mp4). Requires system ffmpeg.

.dv/.dif is a transcode-only input: it is always re-encoded to an .mp4 (H.265 by default), and --fast (copy) is ignored for DV sources.

CodecFlagNotes
H.265 (HEVC)--codec h265 (default)~50% smaller than H.264
H.264 (AVC)--codec h264Maximum compatibility
AV1--codec av1Best compression, slower encode
VP9auto for .webmSelected automatically for WebM containers
Copy--fastNo re-encode, strips metadata only

Audio streams are copied as-is (no audio re-encoding).

Audio

Supported via ffmpeg + ffprobe: MP3, AAC/M4A, WAV, FLAC, OGG, Opus, AIFF, WebM-audio. Tags and album art are preserved by default.

CodecFlagNotes
MP3--codec mp3LAME VBR quality scale
AAC--codec aacBitrate ladder (default 192 kbps at q=80)
Opus--codec opusModern lossy codec; default for lossless inputs in non-interactive mode
Vorbis--codec vorbisQuality scale, in .ogg
FLAC--codec flacLossless re-encode
ALAC--codec alacLossless, in .m4a

By default, lossy inputs (MP3/AAC/etc) re-encode to the same codec; lossless inputs (FLAC/WAV/AIFF) prompt once for a target codec (defaults to Opus in non-interactive mode).

Code

Minifies JavaScript, TypeScript, CSS, HTML, and JSON via pure-Rust libraries — no Node runtime required.

LanguageLibraryDefault behavior
JS / TSoxc_minifierMangle + DCE; --safe for whitespace-only
CSSlightningcssWhitespace + comment removal, normalization
HTMLminify-htmlWhitespace + comment removal; preserves <script> content
JSONserde_jsonWhitespace removal; rejects JSON5/JSONC

Output uses .min suffix with . separator (industry convention): app.jsapp.min.js. TypeScript and JSX inputs become .js (types are erased; JSX is compiled).

SVG continues to be handled as an image (structural compaction via oxvg_optimiser, an SVGO-equivalent).

Flags

FlagWhat it does
-q, --quality <0-100|auto> Quality, or auto for the lowest visually-lossless quality (images only; conflicts with --target-size)
--lossless Lossless compression (overrides --quality)
-f, --format <FORMAT> Output format (image/video/audio); applied per input kind
--max-width <PIXELS> Scale down images wider than this (preserves aspect ratio)
--max-height <PIXELS> Scale down images taller than this (preserves aspect ratio)
--width <PIXELS> Render vector (SVG) input at this pixel width. Unlike --max-width this upscales — a vector has no native resolution. Ignored for raster input
--height <PIXELS> Render vector (SVG) input at this pixel height. See --width
--crop <SPEC> Crop images before compressing (applied before --max-width/--max-height). An aspect ratio like 16:9 or 1:1 (largest fit, anchored by --gravity), or an exact pixel rect WxH+X+Y, e.g. 800x600+120+40. Images only; GIF crops preserve animation. Not read from squish.toml
--gravity <POS> Anchor for an aspect-ratio --crop: center (default), north, south, east, west, northwest, northeast, southwest, southeast
--select Pick the crop region interactively in your browser (single image only; seeded by --crop/--gravity). Shows the selection's size in source pixels and the exact output size it will produce. Conflicts with --json/--watch/--stats
--target-size <SIZE> Per-file output size budget, e.g. 500k, 1.5M, 2g (decimal units). Images pick the highest quality that fits; video/audio compute a bitrate from the input's duration. Conflicts with --quality/--lossless/--bitrate/--fast; not applicable to code files or lossless audio codecs
-r, --recursive Recurse into directories
--force Overwrite existing _squished files
-o, --overwrite Replace each input file in place (skips files whose squish would change the extension, e.g. .dv→.mp4)
--suffix <NAME> Custom output filename suffix (default: squished), e.g. squish --suffix tiny app.jsapp.tiny.js
--dry-run Show what would happen; don't write
--watch Keep running: watch the given paths and squish files as they appear or change (Ctrl-C to stop). Never re-squishes its own outputs
--keep-metadata Preserve EXIF and the ICC colour profile in image output (default: EXIF stripped, ICC always preserved). Orientation is applied to pixels before encoding either way, so framing is correct regardless. JPEG and PNG only
--json Print a single machine-readable JSON report to stdout instead of the human summary (warnings still go to stderr). Works with --dry-run; exit code is unaffected
--exclude <GLOB> Skip files/dirs matching this glob during a directory walk (repeatable). Matched relative to each input path's own root; explicit file arguments are never excluded
--gitignore Also respect .gitignore (plus .git/info/exclude and the global gitignore) while walking directories. Off by default
--no-default-excludes Don't prune .git, node_modules, and target while walking directories (pruned by default)
--no-config Ignore squish.toml config files for this run
--kinds <KINDS> Restrict the run to these file kinds, comma-separated: image, video, audio, code (default: all)
--stats Print usage report (this month + all-time) and exit
--no-stats Skip recording this run (also: SQUISH_NO_STATS=1)
-j, --jobs <N> Parallelism (default: num CPUs)
-v, --verbose Per-file output
--quiet Errors only
--codec <CODEC> Codec: video=h264|h265|av1|vp9, audio=mp3|aac|opus|vorbis|flac|alac
--fast Video: optimise without re-encoding
--bitrate <BITRATE> Audio bitrate, e.g. 128k, 192k. Overrides --quality for lossy audio
--strip-tags Strip audio metadata (ID3 tags, album art). Default: preserved
--preset <web> Apply a destination preset of sensible defaults (overridable by explicit flags). Currently: web
--safe Code: skip mangling and DCE (whitespace-only minification)
--source-map Code: emit a .map file alongside output (JS/TS/CSS only)

Config file

squish reads defaults from the nearest squish.toml (walking up from the current directory) and from a global config at ~/Library/Application Support/squish/config.toml (macOS) or ~/.config/squish/config.toml (Linux). Pass --no-config to ignore both. Keys mirror the CLI flag names.

Precedence: CLI flags > project squish.toml > global config.

# squish.toml
quality = 75          # or "auto" — perceptual visually-lossless (images only)
format = "webp"
recursive = true
max-width = 2000
# Replace originals in place instead of writing _squished siblings.
# Destructive — no copy is kept. CLI flags still override this.
overwrite = false

[video]
codec = "h264"

[audio]
codec = "opus"
strip-tags = true

[code]
safe = true

quality accepts a number (0–100) or the string "auto" (perceptual visually-lossless, images only; conflicts with target-size). The squish config wizard offers auto as an option.

Rate control is all-or-nothing: passing any of --quality/--lossless/--bitrate/--fast/--target-size on the command line disables all of those keys from config for that run, so a config target-size can never override an explicit --quality. Unknown keys are an error — typos fail loudly.

Interactive setup

Don't want to hand-edit TOML? Run the wizard:

squish config            # edit the global config
squish config --local    # edit ./squish.toml for this project instead

It walks through quality, format, suffix, recursive, strip-tags, and overwrite, pre-filling whatever is already set (press Enter to keep a value, - to clear it). Note: the file is rewritten, so any hand-written comments in it are not preserved.

GitHub Action

Squish assets in CI with the bundled action — handy before deploys, or paired with a commit-back step:

- uses: MikeDre/squish@v0.10.1
  with:
    paths: public/images
    args: "--recursive --overwrite --quality 75"
InputDefaultNotes
paths(required)Files or directories, space-separated
args""Any squish CLI flags
versionlatestRelease tag of the binary to download
install-depstrueapt/brew runtime deps (libheif, dav1d, ffmpeg, gifsicle)

Runs on ubuntu and macos runners (x64 + arm64).

Finder Quick Action

Add a "Squish" entry to Finder's right-click menu — for the folks who never open a terminal:

squish finder-action install

Select files or folders in Finder → right-click → Quick Actions → Squish. It squishes media (images, video, audio — never code) with your usual defaults: _squished siblings, originals untouched, squish.toml respected.

A notification reports progress and the final savings. Remove it any time with squish finder-action uninstall; re-run install after moving or reinstalling squish.

Collision behavior

If dog_squished.png already exists, squish writes dog_squished_2.png, then _3, etc. Pass --force to overwrite instead.