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

Compiles squish from crates.io (stable Rust 1.95+) and places the binary in ~/.cargo/bin. 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

# 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

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)
GIF (static + animated)gifsicle -O3
HEIClibheif-rs
TIFFinput only — defaults to re-encoding as JPEG; use --format tiff to keep TIFF output

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)
--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
--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.7.0
  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.