TinyJPG
May 18, 2026 ยท View on GitHub
TinyJPG
TinyJPG is a C++23 image optimizer built for fast, repeatable asset pipelines. It compresses JPEG, PNG, WebP, AVIF, and JPEG XL files in-process, generates responsive variants, scans folders, watches upload directories, and reports results as text, tables, or JSON.
The executable is available as both tinyjpg and the short alias tj.
Contents
- Preview
- Why TinyJPG
- Install
- Quick Start
- Commands
- Configuration
- Services
- Release Assets
- Build From Source
Preview
| Before | After |
|---|---|
![]() | ![]() |
| 1.5 MB JPEG | 243 KB JPEG |
This sample keeps the original 2236 x 1792 dimensions and reduces the file by about 84%.
Why TinyJPG
- Native C++23 command line tool with no shell-outs for image conversion.
- Codec coverage for JPEG, PNG, WebP, AVIF, and JPEG XL workflows.
- One-shot
run, recursivescan, and continuouswatchmodes. - Built-in responsive presets plus TOML configuration for custom variants.
- Atomic writes, dry-run planning, skip-if-not-smaller behavior, and structured output for CI and automation.
- Cross-platform CMake and vcpkg build on Linux, macOS, and Windows.
Install
Linux and macOS:
curl -fsSL https://tj.eorlov.org/install.sh | sh
Windows PowerShell:
irm https://tj.eorlov.org/install.ps1 | iex
Homebrew on Apple Silicon macOS:
brew tap OrlovEvgeny/tinyjpg
brew install tinyjpg
Install a specific version:
curl -fsSL https://tj.eorlov.org/install.sh | sh -s -- --version 1.0.1
irm https://tj.eorlov.org/install.ps1 -OutFile install.ps1
./install.ps1 -Version 1.0.1
The installers read the release manifest, pick the correct archive for the
current platform, verify SHA256, and install tinyjpg plus the tj alias.
Quick Start
Run a safe dry-run first:
tj scan ./images --preset web --dry-run --format table
Generate optimized variants:
tj scan ./images --preset web --format table
Create a config file for repeatable jobs:
tj config print --defaults > tinyjpg.toml
tj config validate tinyjpg.toml
tj scan ./images --config tinyjpg.toml
Watch a directory continuously:
tj watch ./uploads --config tinyjpg.toml
Commands
| Command | Purpose | Example |
|---|---|---|
run | Optimize explicit files. | tj run hero.jpg banner.png --preset web |
scan | Optimize supported images under files or directories. | tj scan ./public/images --config tinyjpg.toml |
watch | Repeatedly scan paths and process changed images. | tj watch ./uploads --config tinyjpg.toml |
presets list | Show built-in responsive presets. | tj presets list --format table |
config print | Print a default TOML config. | tj config print --defaults |
config validate | Validate a TOML config before automation. | tj config validate tinyjpg.toml |
doctor | Check local runtime basics. | tj doctor --format table |
completion | Print shell completion for bash, zsh, or fish. | tj completion zsh > _tj |
Common options for run, scan, and watch:
| Option | Meaning |
|---|---|
--config, -c | TOML configuration file. |
--preset | Replace configured variants with web, ecommerce, or avatar. |
--dry-run | Plan work without writing files. |
--quiet, -q | Suppress per-file text output. |
--format | Output as text, table, or json. |
Output formats are intended for different uses: text for humans, table for
inspection, and json for CI or other automation.
Configuration
Generate and validate a config:
tj config print --defaults > tinyjpg.toml
tj config validate tinyjpg.toml
TinyJPG starts from built-in defaults and then applies values from your TOML
file. CLI flags such as --preset and --dry-run override the loaded config for
that invocation.
Practical Config
This config is suitable for a service-style image pipeline. It watches an input directory, writes generated files into a separate output directory, and creates three web delivery variants.
[general]
workers = 0
log_level = "info"
queue_capacity = 512
stable_wait_ms = 400
dry_run = false
[watch]
paths = ["/var/lib/tinyjpg/inbox"]
recursive = true
include = ["*.jpg", "*.jpeg", "*.png", "*.webp"]
exclude = ["**/.cache/**", "*.tmp"]
prefix = []
[compress]
mode = "visually_lossless"
effort = "balanced"
keep_metadata = false
skip_if_not_smaller = true
preserve_original = true
[[variant]]
name = "large"
codec = "auto"
max_width = 1920
suffix = "-large"
[[variant]]
name = "card"
codec = "webp"
mode = "lossy"
max_width = 960
quality = 82
suffix = "-card"
[[variant]]
name = "thumb"
codec = "auto"
max_width = 320
max_height = 320
quality = 76
fit = "cover"
suffix = "-thumb"
[output]
directory = "/var/lib/tinyjpg/output"
pattern = "{stem}{suffix}.{ext}"
on_exist = "version"
For /var/lib/tinyjpg/inbox/hero.jpg, this can produce:
/var/lib/tinyjpg/output/hero-large.jpg
/var/lib/tinyjpg/output/hero-card.webp
/var/lib/tinyjpg/output/hero-thumb.jpg
Config Reference
[general]
| Setting | Values | Meaning |
|---|---|---|
workers | 0 or positive integer | Worker threads. 0 uses hardware concurrency. |
log_level | trace, debug, info, warn, error | Runtime verbosity. |
queue_capacity | positive integer | Maximum queued work items before producers wait. |
stable_wait_ms | 0 or positive integer | Watch-mode delay before reading a changed file, so uploads can finish. |
dry_run | true, false | Plan work without writing files. Can also be set with --dry-run. |
[watch]
| Setting | Values | Meaning |
|---|---|---|
paths | array of paths | Default paths for tj watch when no CLI path is passed. |
recursive | true, false | Parsed from TOML; directory scanning currently walks recursively. |
include | glob array | Parsed from TOML; current processing still selects files by supported image codec. |
exclude | glob array | Parsed from TOML; generated TinyJPG variants are skipped automatically. |
prefix | string array | Parsed from TOML and reserved for path-prefix filtering. |
[compress]
| Setting | Values | Meaning |
|---|---|---|
mode | lossless, visually_lossless, lossy | Default fidelity target for variants. |
effort | fast, balanced, max | Encoder effort/speed preference. |
keep_metadata | true, false | Keep image metadata when possible. |
skip_if_not_smaller | true, false | Skip outputs that would be larger than the source. |
preserve_original | true, false | Avoid overwriting the source path; an empty suffix becomes -optimized. |
[[variant]]
Each variant describes one output image. At least one variant is required.
Variant names may contain letters, digits, _, and -.
| Setting | Values | Meaning |
|---|---|---|
name | string | Variant name shown in output and available as {name}. |
codec | auto, jpeg, png, webp, avif, jxl | Output codec. auto keeps the source codec. |
mode | same as [compress].mode | Optional per-variant fidelity override. |
quality | 1 to 100 | Encoder quality. If omitted, TinyJPG uses 82. |
max_width | positive integer | Maximum output width. |
max_height | positive integer | Maximum output height. |
fit | contain, cover, fill | Resize behavior when both dimensions are set. |
suffix | string | Added to output file names, for example -thumb. |
Every non-original variant must set max_width, max_height, or both. The
special variant name original is allowed without size constraints.
[output]
| Setting | Values | Meaning |
|---|---|---|
directory | path or empty string | Output directory. Empty means next to the input file. |
pattern | string with tokens | Output path template. |
on_exist | skip, overwrite, version | Behavior when the output path already exists. |
Supported pattern tokens:
| Token | Value |
|---|---|
{dir} | Input directory or configured output directory. |
{stem} | Input file name without extension. |
{suffix} | Variant suffix. |
{ext} | Output extension for the chosen codec. |
{name} | Variant name. |
{codec} | Output codec name. |
{width} | Planned output width. |
{height} | Planned output height. |
Useful patterns:
pattern = "{dir}/{stem}{suffix}.{ext}"
pattern = "{stem}-{width}w.{ext}"
pattern = "{codec}/{stem}-{name}.{ext}"
Presets
Use presets when you do not need custom variants:
tj scan ./images --preset web
tj scan ./products --preset ecommerce
tj scan ./avatars --preset avatar
| Preset | Variants |
|---|---|
web | original, large 1920w, medium 1024w, thumb 320x320 cover |
ecommerce | original, hero 1600w, listing 900w, thumb 320w |
avatar | original, full 512x512 cover, thumb 128x128 cover |
Services
Release archives include service helpers for long-running optimization:
- systemd unit, sysusers, and tmpfiles snippets for Linux.
- launchd plist template for macOS.
- PowerShell install and uninstall scripts for Windows services.
systemd Example
The generated Linux unit runs:
tinyjpg watch --config /etc/tinyjpg/tinyjpg.toml
It uses a dedicated tinyjpg user, protects the host filesystem, and only grants
write access to /var/lib/tinyjpg and /var/log/tinyjpg. Keep watched input and
output directories under /var/lib/tinyjpg, or add a systemd override with extra
ReadWritePaths=.
Example setup:
sudo systemd-sysusers packaging/systemd/tinyjpg.sysusers
sudo systemd-tmpfiles --create packaging/systemd/tinyjpg.tmpfiles
sudo install -d -m 0750 -o root -g root /etc/tinyjpg
sudo install -d -m 0750 -o tinyjpg -g tinyjpg /var/lib/tinyjpg/inbox
sudo install -d -m 0750 -o tinyjpg -g tinyjpg /var/lib/tinyjpg/output
sudo install -m 0640 -o root -g tinyjpg tinyjpg.toml /etc/tinyjpg/tinyjpg.toml
sudo install -m 0644 build/packaging/systemd/tinyjpg.service /etc/systemd/system/tinyjpg.service
sudo systemctl daemon-reload
sudo systemctl enable --now tinyjpg.service
Check service health and logs:
systemctl status tinyjpg.service
journalctl -u tinyjpg.service -f
If your images live outside /var/lib/tinyjpg, add an override:
sudo systemctl edit tinyjpg.service
[Service]
ReadWritePaths=/srv/uploads /srv/images /var/lib/tinyjpg /var/log/tinyjpg
Then reload and restart:
sudo systemctl daemon-reload
sudo systemctl restart tinyjpg.service
Release Assets
Tagged releases publish platform archives to Cloudflare R2 and create a GitHub Release with changelog and install instructions.
| Platform | Archive |
|---|---|
| Linux x86_64 | tinyjpg-{version}-linux-x86_64.tar.gz |
| Linux aarch64 | tinyjpg-{version}-linux-aarch64.tar.gz |
| macOS arm64 | tinyjpg-{version}-macos-arm64.tar.gz |
| Windows x86_64 | tinyjpg-{version}-windows-x86_64.zip |
The public manifest for installers and future self-update support is:
https://tinyjpg.eorlov.org/tinyjpg/manifest.json
Build From Source
Requirements:
- CMake 3.28 or newer.
- Ninja.
- A C++23 compiler.
- vcpkg.
nasmon Linux for codec dependencies.
Configure, build, test, and install:
cmake -S . -B build -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" \
-DBUILD_TESTING=ON
cmake --build build
ctest --test-dir build --output-on-failure
cmake --install build --prefix ~/.local
Create local CPack archives:
cpack --config build/CPackConfig.cmake

