Skip to content
ahmetomervPublic

About

Command-line tools for photographers.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

129 stars

Watchers

0 watching

Forks

Repository files navigation

photoc: command-line tools for photographers

Version Tests / CI Release / CD License: MIT Platforms: macOS | Linux

ahmetomerv/photoc | Trendshift

Quick start · Commands · Workflows · File safety · Scripting · Installation · Troubleshooting · Contributing · License

photoc

Command-line tools for photographers.

photoc helps you review, organize, and prepare photos from the terminal: summarize a shoot, read metadata, find blurry shots and exact duplicates, rename and sort into folders, make contact sheets and smaller copies, or remove GPS before sharing. Most commands work in shell scripts; review needs a terminal. By default, inline previews appear only in a detected direct iTerm2 session; other terminals stay text-only. Inspection commands can print JSON. Large JPEGs may use temporary in-memory previews, which do not change the originals (review guide).

photoc review in a terminal, showing a photo with metadata and pick, reject, and unmark controls

photoc reads JPEG files and metadata from Sony ARW RAW files. Other RAW formats (CR3, NEF, RAF, DNG, …) and HEIC are not supported yet (File-type support). It does not develop RAW files, edit images, catalog a library, or replace a metadata editor such as ExifTool. Windows is not supported yet.

Originals stay unchanged unless you pass --apply (rename, sort) or --in-place (scrub). Read-only commands only read. Commands that create files write new copies next to your originals or in a folder you choose, and never overwrite unrelated output files (File safety).

photoc query finding low-light shots and wide-angle f/5.6 shots in a folder of photos

Quick start

Install photoc from its Homebrew tap (macOS or Linux with Homebrew):

brew install ahmetomerv/photoc/photoc
photoc --version

The qualified name adds the tap automatically; Homebrew installs the required libraries. For a prebuilt executable instead, use the shell installer.

Then try these commands on your own photos (review saves marks in a separate state file; the others shown here only read):

photoc --help                         # list commands
photoc review ./photos                # interactively mark JPEGs to keep or reject
photoc exif photo.jpg                 # one photo's metadata
photoc stats ./photos --recursive     # summarize a folder and its subfolders
photoc timeline ./photos              # review a shoot by session

For example, photoc rename ./shoot --format '{date}_{camera}_{sequence}.{ext}' previews these three sample JPEGs without changing any files:

session_1000.jpg -> 2026-09-27_Model S_0001.jpg
session_1030.jpg -> 2026-09-27_Model S_0002.jpg
session_1200.jpg -> 2026-09-27_Model S_0003.jpg
Summary: 3 JPEG, 3 planned, 0 unchanged, 0 blocked, 0 applied, 0 rolled back

For help with a specific command, run photoc <command> --help or man photoc.

Directory scans look only inside the chosen folder unless you add --recursive, and they do not follow symlinks. Quote paths containing spaces, such as photoc exif "Summer trip/photo.jpg". Put options before -- when a path begins with -, for example photoc exif -- -photo.jpg.

Commands

Each command name links to its guide with options, examples, limits, and a JSON schema where supported. All guides are listed in the documentation index.

Review (interactive; photo files unchanged):

Command Purpose
review Browse JPEGs, inspect metadata and sharpness, and save picked/rejected marks

Inspect (read-only):

Command Purpose
exif Show dimensions, camera, exposure, capture time, and GPS for one photo
stats Summarize storage, capture dates, cameras, lenses, and exposure settings
timeline Group a shoot by date and session
query Find photos matching metadata filters such as ISO, aperture, camera, or date
duplicates Find byte-identical files and show potential space savings
focus Rank JPEGs by sharpness score, lowest first, to help culling
check Audit JPEGs for structural and decoding problems

Organize (preview by default; add --apply to change files):

Command Purpose
rename Rename photos using metadata templates such as {date}_{camera}_{sequence}.{ext}
sort Move photos into YYYY/MM/DD/ or session-001/ folders

Make copies (originals unchanged by default):

Command Purpose
compress Write smaller JPEG copies at a chosen quality or target file size
contact Make paged JPEG contact sheets with filenames and optional exposure data
scrub Write copies without GPS, private fields, or descriptive metadata

--json works with exif, stats, timeline, query, duplicates, focus, and check. Longer directory operations show progress on stderr; use --no-progress to turn it off.

File-type support

Command JPEG Sony ARW Other RAW files
review Interactive culling; JPEGs unchanged Unsupported/skipped Unsupported/skipped
exif, stats, timeline, rename, sort Metadata Common TIFF/EXIF metadata Unsupported/skipped
query, check, compress, contact, focus, scrub Supported Unsupported/skipped Unsupported/skipped
duplicates Exact bytes Exact bytes Exact bytes

ARW support is metadata only: no RAW development, pixel decoding, compression, or GPS rewriting. Files are matched by extension (.jpg, .jpeg, .arw, case-insensitive). See Sony ARW metadata support for fields and limits. Want your camera supported? See Contributing.

Workflows

These recipes combine commands the way you might use them after a shoot. Commands that change files are shown in two steps: preview, then apply.

After a shoot: review, rename, and sort

# See how the day breaks into sessions. Adjust --gap to match how you shoot.
photoc timeline ./shoot --gap 45m

# Preview new names, then apply the same command.
photoc rename ./shoot --format '{date}_{camera}_{sequence}.{ext}'
photoc rename ./shoot --format '{date}_{camera}_{sequence}.{ext}' --apply

# Preview session folders, then apply.
photoc sort ./shoot --by session --gap 45m
photoc sort ./shoot --by session --gap 45m --apply

If any photo lacks the metadata a plan needs, such as a capture date, the whole plan is blocked and nothing changes. The preview lists the blocked files.

Culling: find blurry shots and duplicates

# Browse the shoot and save picked/rejected marks in .photoc-review.json.
photoc review ./shoot

# Lowest sharpness scores first; show only those below the threshold.
photoc focus ./shoot --recursive --threshold 100 --only-blurry

# Byte-identical copies, for example from importing a card twice.
photoc duplicates ./shoot --recursive

Sharpness scores are a review aid, not a verdict: subject detail, noise, and intentional blur all affect them. photoc never deletes files; you decide what to remove.

Before sharing: smaller copies without location

# Write compressed copies into ./share, keeping the originals.
photoc compress ./selects --recursive --target 2MB --output-dir ./share

# Remove location and identifying metadata from those copies.
photoc scrub ./share --privacy --recursive --in-place

# Confirm no copy still has GPS (prints nothing when clean).
photoc query ./share --recursive --has-gps

compress keeps EXIF, including GPS, by default, so scrub after compressing. --in-place is used here only because ./share contains copies; it replaces files without a backup. --privacy can leave data in opaque MakerNotes; see the scrub limits.

Find specific shots

photoc query ./photos --recursive --iso ">800" --aperture "<=4"
photoc query ./photos --camera "DSC-RX100M7A"
photoc query ./photos --after 2026-01-01 --before 2026-12-31

Filters are combined with AND, and date bounds include the named days. See query details for matching rules and NUL-separated output.

More examples

# Contact sheet with exposure data, sorted by capture time.
photoc contact ./shoot --output sheet.jpg --metadata --sort date

# Check a folder for corrupt or truncated JPEGs.
photoc check ./photos --recursive --only-errors

# Save statistics as JSON for your own scripts.
photoc stats ./photos --recursive --json > stats.json

File safety

Command Changes originals? Output Existing files
review Never Review-state JSON, default ./shoot/.photoc-review.json Refuses unrelated state files; atomically replaces its own valid state
Inspect commands Never Report on stdout Not touched
rename, sort Only with --apply Renames or moves in place Never overwritten; whole plan checked first
compress Never photo.compressed.jpg copies Skipped (directory) or rejected (single file)
contact Never sheet.jpg, sheet-001.jpg, … Never overwritten
scrub Only with --in-place photo.scrubbed.jpg copies Rejected

Important details:

  • Review saves only picked/rejected marks in a versioned state file. It never changes JPEG data, filenames, or locations; quitting without a mark creates no state file. See interactive review.
  • Rename and sort check the whole plan before changing anything. If a rename or move fails, photoc tries to undo earlier changes, but another process changing files at the same time can prevent full recovery. Keep backups of important collections.
  • Compress loses image detail and can produce a larger file. EXIF (including orientation and GPS), ICC color profiles, and XMP are preserved; other APP markers are not copied. If a --target size cannot be reached, photoc still writes a copy at the minimum quality and exits with status 1. 2MB means 2,000,000 bytes; 2MiB means 2,097,152 bytes. See metadata preservation.
  • Compress memory scales with one file at a time: the source JPEG, the decoded RGB image (3 * width * height bytes), a metadata snapshot up to 64 MiB, and the encoded output. A --target search retains one winning encode between probes; a new candidate can briefly coexist with it. Large images use proportionally more memory. See memory use.
  • Scrub copies JPEG image data without recompression. --gps removes EXIF GPS only; --privacy removes supported location and identifier fields; --all-metadata removes descriptive metadata but keeps ICC profiles and orientation. Information in opaque MakerNotes or unknown formats may remain.
  • scrub --in-place creates no backup. It verifies a temporary copy, then replaces the original in one atomic step. Symlinks, hard links, and files that change during processing are refused. Permission bits and group ownership are kept; ACLs and extended attributes are not copied.
  • Check reads without changing anything. An OK result is not a backup or a visual-quality guarantee. See audit limits.

Scripting

Results go to stdout; status text, warnings, errors, and progress go to stderr. --json output contains only JSON, and missing values are null.

photoc exif photo.jpg --json | jq '.exposure'
photoc timeline ./photos --json | jq '.summary'
photoc query ./photos --recursive --has-gps --print0 | xargs -0 ls -l
Exit code Meaning
0 Success
1 A processing or filesystem operation failed, or a compression target was not met
2 Invalid command usage

Global options work before or after the command: -q/--quiet keeps results but hides status text and non-critical warnings, -v/--verbose adds diagnostics on stderr, and --no-progress disables the progress display.

The scripting guide covers JSON details, per-command exit code rules, progress, and exactly what quiet and verbose modes show.

Installation

Homebrew (recommended)

The Quick start uses the project's Homebrew tap. It builds photoc from source and manages its dependencies. After a new version is published to the tap, update an existing Homebrew installation with:

brew update
brew upgrade photoc
photoc --version

brew update refreshes the tap; brew upgrade photoc installs the newer version. The version badge at the top tracks GitHub releases; Homebrew offers that version after the update pull request for the tap formula is merged. See the installation guide for details.

Shell installer

The shell installer downloads a checksum-verified prebuilt release for macOS arm64, macOS x86_64, or Linux x86_64 to $HOME/.local/bin. Install the runtime libraries for your system first:

# macOS
brew install libexif jpeg-turbo libxml2
# Ubuntu / Debian
sudo apt install libexif12 libturbojpeg libjpeg8 libxml2

Then download and run the installer:

curl -fsSL https://raw.githubusercontent.com/ahmetomerv/photoc/main/scripts/install.sh \
  -o install-photoc.sh
sh install-photoc.sh
photoc --version

Release binaries require macOS 15 or later, or glibc-based Linux with glibc 2.35 or later. The installer verifies the download's SHA-256 checksum and never replaces an existing file. If photoc is not found, see Add photoc to your PATH.

  • Choose a version or directory: sh install-photoc.sh --version vX.Y.Z --install-dir "$HOME/bin"

  • Upgrade: preview and remove the tracked shell installation, then run the installer again; see Upgrading.

  • Uninstall: download the uninstaller, preview, then apply:

    curl -fsSL https://raw.githubusercontent.com/ahmetomerv/photoc/main/scripts/uninstall.sh \
      -o uninstall-photoc.sh
    sh uninstall-photoc.sh          # preview
    sh uninstall-photoc.sh --apply  # remove

    This removes only an unchanged shell-installer installation.

The installation guide also covers source builds, shell completions, install locations, the man page, and upgrade behavior. Source and manual installations have their own uninstall instructions.

Troubleshooting

photoc: command not found

Check that your install directory is on PATH. For the shell installer, see Add photoc to your PATH.

Which photoc installation am I running?

Run type -a photoc to list matching commands and command -v photoc to see which one your shell selects. A Homebrew installation normally resolves to $(brew --prefix)/bin/photoc. If an older shell-installed copy also appears at ~/.local/bin/photoc, follow the tracked uninstall steps. The same path appearing twice in type -a usually means its directory occurs twice in PATH.

error while loading shared libraries: libturbojpeg.so.0 (Linux) or dyld: Library not loaded (macOS)

A runtime library is missing from a shell or manual installation. Install the packages listed under Shell installer. The prebuilt executable does not bundle these libraries.

macOS says the binary "cannot be opened" or "cannot be verified"

Release binaries are not signed or notarized. The installer downloads with curl, which does not trigger this. If you downloaded a binary in a browser, remove the quarantine flag after verifying its checksum: xattr -d com.apple.quarantine ./photoc-darwin-arm64.

My photos are skipped or reported as unsupported

photoc reads JPEG files and Sony ARW metadata only, matched by file extension. See File-type support.

Rename or sort says the plan is blocked

At least one photo is missing metadata the plan needs, such as a capture date or a field used in your template. The preview names each blocked file. Move those files elsewhere or choose a template that does not need the missing field.

A scan succeeded but some files had warnings

stats and timeline continue past individual file errors and count them in the summary. See exit codes.

For anything else, open an issue with the command you ran, photoc --version, your OS, and the full error output.

Contributing

Bug reports, documentation fixes, and focused code changes are welcome. You can also help without writing C: photographers can share sample files from other cameras so support for more formats can be tested.

For changes, open a pull request from a branch into main and follow the contributor checks.

Please follow the code of conduct. For file corruption, unsafe overwrites, or suspected vulnerabilities, follow the private reporting steps in SECURITY.md.

License

photoc's source is licensed under the MIT License. Dependencies retain their own licenses; see third-party notices. This software is based in part on the work of the Independent JPEG Group.

About

Command-line tools for photographers.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

129 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages