Skip to main content

pix

Batch AVIF shrink and image-host sync CLI.

pix incrementally compresses local image directories to AVIF, mirrors originals to cloud storage, and publishes compressed images to an image host. Compression uses pyvips; sync is delegated to rclone.

中文 · Architecture · Development · Decisions · Roadmap

Status: core commands are implemented and tested. Real R2 and Windows smoke tests are still pending; see the roadmap.

What pix does

Command Purpose
pix shrink Recursively compress images to AVIF, re-encoding only changed files
pix backup Bidirectionally mirror an original photo directory to a cloud drive
pix publish Bidirectionally mirror AVIF output to an image host; optionally emit public URLs
pix doctor Check Python, libvips, rclone and configuration
pix config init Write a commented default configuration file

pix is deliberately narrow:

  • It is not an rclone replacement and does not manage credentials.
  • It does not read repository config or care about version control.
  • It has no one-shot pix run; you chain the commands yourself.

How it works

photos/ ── shrink (pyvips) ──> out/*.avif + out/.pix/state.json
                                     │
                                     └── publish (rclone bisync) ──> r2:images
photos/ ── backup (rclone bisync) ────────────────────────────────> onedrive:photos
Command What actually runs Who does the work
shrink pyvips thumbnail + AVIF encode pix
backup rclone bisync SRC REMOTE pix composes arguments, rclone executes
publish rclone bisync OUT REMOTE, excluding .pix/** pix + rclone
doctor / config init Dependency checks / config scaffold pix

Sync is rclone. backup and publish only compose rclone bisync arguments; rclone handles authentication, transfers and conflict handling. Configure remotes with rclone config before using them. pix never reads or writes rclone.conf.

backup / publish are bidirectional mirrors, not one-way backups. Deletions propagate in both directions.

Requirements

  • Python 3.11+
  • uv
  • rclone >= 1.66 for backup / publish
  • At least one remote already configured in rclone

pyvips[binary] bundles libvips, so no separate vips installation is required.

Install

Not yet published on PyPI; install from source:

git clone https://github.com/uzvg/pix.git
cd pix
uv tool install --editable .
pix --version

Linux installs ~/.local/bin/pix; Windows installs %USERPROFILE%\.local\bin\pix.exe. An editable install picks up source changes immediately. Reinstall with uv tool install --force --editable ., or remove it with uv tool uninstall pix-cli.

Configure rclone

Only needed for backup / publish:

rclone config         # create a remote, for example onedrive or r2
rclone lsd onedrive:  # verify that it works

rclone stores credentials in ~/.config/rclone/rclone.conf (Windows: %APPDATA%\rclone\rclone.conf). pix passes the remote name through and does not store or validate credentials.

Configure pix (optional)

Default configuration paths:

  • Linux/macOS: $XDG_CONFIG_HOME/pix/config.toml, or ~/.config/pix/config.toml
  • Windows: %APPDATA%\pix\config.toml

If the file is missing, built-in defaults are used. Generate a commented file with:

pix config init

Existing files are not overwritten; pass --force to replace one. Use --config PATH before the subcommand to point at another file.

Common keys:

Section Key Default Meaning
[shrink] width 1920 Longest edge in pixels
quality 85 AVIF quality, 1–100
effort 5 libvips effort, 0–9
min_size 0 Skip sources smaller than this; accepts 500K, 1M, 2G
include jpg, jpeg, png, webp Extensions to scan, without dots
[backup] exclude [] rclone exclude globs; command-line values are appended
max_delete 10 Delete limit as a percentage; -1 disables, 0 forbids
[publish] base_url "" Public URL prefix; must start with http:// or https://
urls false Emit a JSON URL manifest
exclude [] Same as backup
max_delete 10 Same as backup
[rclone] binary rclone rclone executable name or path

Precedence is command line > pix config > built-in defaults. --exclude is appended to config excludes rather than replacing them. -q means quality; silence is --quiet (no short form).

Quick start

  1. Check the environment:

    pix doctor
    
  2. Compress images:

    pix shrink -s ~/Pictures -d ~/Pictures-avif --dry-run
    pix shrink -s ~/Pictures -d ~/Pictures-avif -w 1920 -q 85
    

    The first run encodes everything; later runs skip unchanged files. State lives in OUT/.pix/state.json. Changing width, quality or effort triggers a full re-encode. --dry-run only prints the plan.

  3. Back up originals:

    pix backup -s ~/Pictures -d onedrive:photos --dry-run
    pix backup -s ~/Pictures -d onedrive:photos --resync
    

    --resync is required on the first run. Always preview with --dry-run first.

  4. Publish the AVIF output:

    pix publish -s ~/Pictures-avif -d r2:images \
      --base-url https://img.example.com --urls > urls.json
    

    publish requires at least one .avif in the source and always excludes .pix/**. The URL manifest is printed to stdout as JSON only after a successful, non-dry-run sync.

  5. Re-run later:

    pix shrink -s ~/Pictures -d ~/Pictures-avif
    pix backup -s ~/Pictures -d onedrive:photos
    pix publish -s ~/Pictures-avif -d r2:images --urls
    

    Only changes are encoded and transferred.

Command cheat sheet

Task Command
Preview compression pix shrink -s SRC -d OUT --dry-run
Re-encode everything pix shrink -s SRC -d OUT --force
Remove outputs whose source is gone pix shrink -s SRC -d OUT --prune
Preview a sync pix backup -s SRC -d REMOTE --dry-run
First run, or after changing excludes pix backup -s SRC -d REMOTE --resync
Emit a URL manifest pix publish -s OUT -d REMOTE --base-url https://img.example.com --urls
Diagnose the environment pix doctor
Generate a config file pix config init

Safety rules

Situation What happens Protection
Deletions on either side Propagated in both directions; accidental deletion is irreversible Preview with --dry-run; default --max-delete 10 (percentage)
First sync rclone has no baseline and errors Add --resync explicitly
Excludes changed The old baseline may treat excluded files as deletions Run with --resync again after changing excludes
Empty source backup / publish refuse; shrink --prune also refuses Use --allow-empty (plus --resync for sync) only when intentional
a.jpg and a.jpeg both exist Both map to the same output Detected before encoding; exits 2
Two concurrent shrinks They would write the same state and outputs OUT/.pix/lock blocks the second process
State lost or corrupt Warning, then a full rebuild Only causes re-encoding; outputs stay valid

Output and exit codes

  • Logs and progress go to stderr.
  • stdout carries doctor reports, --dry-run plans and --urls JSON.
  • Exit codes: 0 success, 1 runtime failure, 2 argument or configuration error.
  • backup / publish return rclone's exit code unchanged.

URL manifest example:

{
  "2024/a.avif": "https://img.example.com/2024/a.avif"
}

Troubleshooting

Symptom Fix
pyvips/libvips unavailable Reinstall to bring libvips: uv tool install --force --editable /path/to/pix; or install libvips manually (Linux: apt install libvips42, Windows: libvips release zip on PATH)
rclone is older than 1.66 Upgrade rclone
bisync reports a missing baseline Add --resync on the first run
OUT/.pix/lock already exists Check for another shrink, then remove a stale lock
Configuration fails to parse Run pix doctor to locate the file; fix the TOML or remove the file to fall back to defaults
publish says the source has no .avif Run pix shrink first
URL-related error base_url must start with http:// or https://, and --urls requires it

Development

uv sync
uv run pytest
uv run pix --version
uv build

The code must stay compatible with Python 3.11. See development.md for tests, packaging and release details. Behavior is specified in architecture.md; rationale lives in decisions.md.

The detailed docs are currently written in Chinese.

Metadata

Release files for pix-cli 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pix-cli 0.1.1
File Size Uploaded Provenance Actions
pix_cli-0.1.1.tar.gz 52.5 kB With provenance Details for pix_cli-0.1.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for pix-cli 0.1.1
File Interpreter ABI Platform Actions
pix_cli-0.1.1-py3-none-any.whl Python 3 none any Details for pix_cli-0.1.1-py3-none-any.whl

Total release size: 70.5 kB

Release files / pix_cli-0.1.1.tar.gz

Download URL pix_cli-0.1.1.tar.gz
Size 52.5 kB
Tags Source
SHA-256 checksum
How to use checksums
412e689fdaf47baa4be21b271ac27431e5d8700f410c473bd3fb44ea52b79b0a
BLAKE2b-256 checksum
How to use checksums
6904c3507b549c04aedd3e2b89d6139e55fd42fa087c9e12e188739d3dc81d06
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.7 {"installer":{"name":"uv","version":"0.13.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 4, 2026.

Transparency log

Release files / pix_cli-0.1.1-py3-none-any.whl

Download URL pix_cli-0.1.1-py3-none-any.whl
Size 18.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
154eb3ceea1bd2dbd75e8455b181300c24da686d7aa1fdb0e215096dda52ba5a
BLAKE2b-256 checksum
How to use checksums
cda031124ca7368c52d9ef48e853ddb12b653a5fd0c0d37066acb246d193792d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.7 {"installer":{"name":"uv","version":"0.13.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 4, 2026.

Transparency log

Security for pix-cli 0.1.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for pix_cli-0.1.1.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 4, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page