Skip to content

Releases: msetkowski/contact-sheet

v0.7.0

Choose a tag to compare

@github-actions github-actions released this 02 Nov 22:37

Put a title, a date and sheet numbers on your contact sheets: this release adds a header and footer, each with its own text, alignment and font size.

New

Header and footer – --header, --footer

contact-sheet --header "Roll 42" --header-align left \
  --footer "{dd}.{mm}.{yyyy} – sheet {page}" --footer-align right ~/Scans/roll-042

The header and footer are set independently:

Option Default What it does
--header TEXT, --footer TEXT none Text in the top or bottom margin of every sheet
--header-align, --footer-align center left, center or right
--header-size PT, --footer-size PT 5 Font size in points, 1 to 20; decimals such as 6.5 work

Both sit in the page margins, so they do not change how many photos fit on a page. They work with JPEG and PDF output.

Placeholders

Placeholder Replaced with
{page} Sheet number; it keeps counting across split PDF files
{dd} Current day, two digits
{mm} Current month, two digits
{yyyy} Current year

The date is the computer's local date when the run starts, so a run past midnight keeps one date on all its sheets. Anything else in braces is printed as it is.

Text that is too long

Text wider than the space between the side margins is shortened in the middle, like long file names, so a sheet number at the end stays visible. The program warns on stderr, once for the header and once for the footer rather than on every sheet:

Warning: the header is too wide for the page and was trimmed, first on sheet 1: A very long header…for the archive 1

The warning does not change the exit status.

Fixed

  • The script that generates THIRD-PARTY-LICENSES.txt no longer uses the regex substitution flagged by a CodeQL security scan (rb/incomplete-multi-character-sanitization). The script is a release tool and is not part of the program, and the generated notices are unchanged.

Install

brew install msetkowski/tap/contact-sheet   # macOS and Linux
cargo binstall contact-sheet                # prebuilt binary via Cargo
cargo install contact-sheet                 # build from crates.io (Rust 1.89+)

Or download an archive:

Platform Archive
macOS (Apple silicon) contact-sheet-v0.7.0-aarch64-apple-darwin.tar.gz
macOS (Intel) contact-sheet-v0.7.0-x86_64-apple-darwin.tar.gz
Linux (x86_64, static) contact-sheet-v0.7.0-x86_64-unknown-linux-musl.tar.gz
Linux (ARM64, static) contact-sheet-v0.7.0-aarch64-unknown-linux-musl.tar.gz
Windows (x86_64) contact-sheet-v0.7.0-x86_64-pc-windows-msvc.zip

Each archive has a .sha256 checksum next to it. On macOS, run xattr -d com.apple.quarantine contact-sheet before the first start of a downloaded binary (not needed with Homebrew).

Full Changelog: v0.6.1...v0.7.0

v0.6.1

Choose a tag to compare

@github-actions github-actions released this 26 Oct 20:35

A small follow-up to v0.6.0 with clearer output for scripts, based on review feedback. Also the first release that is installable with Homebrew.

Fixed

--progress shows whether each photo worked

A file that could not be read used to get a progress line just like a loaded photo, so the lines did not add up with the summary. Each line now carries a status:

[1/11] skipped broken.jpg
[2/11] ok IMG_0010.jpg
[3/11] ok IMG_0005.jpg

The count always goes up by one from line to line. Photos load in parallel, so they appear in the order they finish, which varies between runs.

Note

If you parse --progress output from v0.6.0 ([n/total] name), the name now follows the status word ok or skipped.

Error messages without repetition

Some errors printed their cause twice, e.g.

Skipped broken.jpg: cannot read image file: Format error decoding Jpeg: Error parsing image. Illegal start bytes:6761: Error parsing image. Illegal start bytes:6761

The image library includes the decoder's error in its own message and also reports it as the underlying cause. Causes already contained in the message are now left out:

Skipped broken.jpg: cannot read image file: Format error decoding Jpeg: Error parsing image. Illegal start bytes:6761

Documentation

The options table in the README now lists all short flags (-r, -p, -l, -c, -d, -f, -q, -b), as --help does.

Install

brew install msetkowski/tap/contact-sheet   # macOS and Linux
cargo binstall contact-sheet                # prebuilt binary via Cargo
cargo install contact-sheet                 # build from crates.io (Rust 1.89+)

Or download an archive:

Platform Archive
macOS (Apple silicon) contact-sheet-v0.6.1-aarch64-apple-darwin.tar.gz
macOS (Intel) contact-sheet-v0.6.1-x86_64-apple-darwin.tar.gz
Linux (x86_64, static) contact-sheet-v0.6.1-x86_64-unknown-linux-musl.tar.gz
Linux (ARM64, static) contact-sheet-v0.6.1-aarch64-unknown-linux-musl.tar.gz
Windows (x86_64) contact-sheet-v0.6.1-x86_64-pc-windows-msvc.zip

Each archive has a .sha256 checksum next to it. On macOS, run xattr -d com.apple.quarantine contact-sheet before the first start of a downloaded binary (not needed with Homebrew).

Full Changelog: v0.6.0...v0.6.1

v0.6.0

Choose a tag to compare

@github-actions github-actions released this 26 Oct 19:55

Choose where the sheets go and what they are called, follow progress photo by photo, read huge film scans – and Intel Mac binaries are back. Shaped by user feedback.

New

Output folder and file name – --output-folder, --name

contact-sheet -o ~/Sheets -n wedding ~/Pictures/2026-10-17
# → ~/Sheets/wedding01.jpg, wedding02.jpg, … or ~/Sheets/wedding.pdf with --format pdf
  • -o, --output-folder DIR – any folder, created if needed (default: the contact-sheet subfolder, as before)
  • -n, --name NAME – base name of the files: NAME01.jpg, … NAME.pdf, NAME-01.pdf (default: contact)
  • Safe in folders full of photos: only names exactly as the program writes them count as its output. With -n IMG_, IMG_07.jpg is a sheet but IMG_0007.jpg is not – photos in the output folder are never overwritten or deleted, and earlier sheets there are never read as photos.

Progress per photo – --progress

Prints one line for every photo as it is processed, skipped ones included:

[12/340] 2026-10-17/IMG_0001.CR2

Handy for progress bars in scripts and programs that run contact-sheet.

Large film scans

TIFF and JPEG files over 512 MiB of pixels – e.g. high-resolution 16-bit film scans – used to be skipped with "Memory limit exceeded". They are now read. Such images are decoded one at a time and shrunk straight away, so a folder full of multi-GB scans does not run out of memory, while smaller files still load in parallel.

macOS (Intel) binaries are back

Prebuilt downloads for Intel Macs are available again (dropped in v0.4.0), including through cargo binstall.

Changed

Important

Exit status 2 when files were skipped. The run now ends with a summary such as Done: 118 photo(s) on 5 page(s), 2 file(s) skipped, and the exit status says how it went:

Exit status Meaning
0 Every photo is on the sheets
1 Error – no sheets were written
2 Sheets were written, but some files could not be read (each is listed on stderr)

Scripts that treat any non-zero status as failure will now notice skipped files.

  • JPEG sheets from an earlier run are no longer deleted before the run starts. After a successful run, only the surplus pages are removed (e.g. contact07.jpg when there are now six pages); the others have been overwritten.

Licences

Release archives now include THIRD-PARTY-LICENSES.txt with the licences of all libraries built into the program. Most are permissive (MIT, Apache-2.0, BSD, Zlib); rawler, which decodes the RAW files, is under the GNU LGPL 2.1 – the file explains where to get its source code and how to build contact-sheet with a modified version.

Install

cargo binstall contact-sheet   # prebuilt binary
cargo install contact-sheet    # build from crates.io (Rust 1.89+)

Or download an archive:

Platform Archive
macOS (Apple silicon) contact-sheet-v0.6.0-aarch64-apple-darwin.tar.gz
macOS (Intel) contact-sheet-v0.6.0-x86_64-apple-darwin.tar.gz
Linux (x86_64, static) contact-sheet-v0.6.0-x86_64-unknown-linux-musl.tar.gz
Linux (ARM64, static) contact-sheet-v0.6.0-aarch64-unknown-linux-musl.tar.gz
Windows (x86_64) contact-sheet-v0.6.0-x86_64-pc-windows-msvc.zip

Each archive has a .sha256 checksum next to it. On macOS, run xattr -d com.apple.quarantine contact-sheet before the first start (the binary is not signed).

Full Changelog: v0.5.0...v0.6.0

v0.5.0

Choose a tag to compare

@github-actions github-actions released this 26 Oct 18:33

Whole photo archives in one run: recursive scanning, format filtering, A3 and Letter paper, and PDF output that scales to thousands of pages. Plus a Linux ARM64 build.

New

Scan subfolders – --recursive [DEPTH]

--recursive scans all subfolders, --recursive 2 goes at most two levels down. Hidden folders and contact-sheet output folders from earlier runs are skipped. Labels and --verbose show the path relative to the scanned folder, e.g. 2026-10-17/IMG_0001.CR2.

Only some formats – --only FORMAT

For cameras that save RAW + JPEG, so every photo appears once:

contact-sheet --only raw ~/Pictures/2026-10-17   # all RAW formats and DNG
contact-sheet --only jpg ~/Pictures/2026-10-17   # .jpg and .jpeg
contact-sheet --only cr3,dng ~/Pictures/archive --recursive

Accepted values: raw, jpg, tif or a single RAW extension (cr2, cr3, crw, nef, nrw, raf, 3fr, fff, dng); case does not matter. The program reports how many files the filter left out.

Paper size – --paper a4|a3|letter

A4 stays the default. Works for JPEG and PDF output, in portrait and landscape.

PDF output for large archives

  • Memory use no longer grows with the page count: pages are written to disk as soon as they are rendered instead of being kept in memory (a 400-page PDF now needs the same memory as JPEG output).
  • Split into several files rather than one that viewers and print shops cannot open: contact-01.pdf, contact-02.pdf, … once a file reaches --max-pdf-size (500 MB by default) or --max-pages. A single file is still named contact.pdf.
  • After the first page the program estimates the total size and tells you when the output will be split; it warns if you allow files over 1 GB.
  • Safe when interrupted: PDFs are written under temporary names and replace the previous output only once complete, so a cancelled run never leaves a broken PDF behind.

Linux ARM64

Prebuilt static binary for 64-bit ARM Linux – e.g. Raspberry Pi 4/5 with a 64-bit OS, AWS Graviton or Ampere servers – built and tested on native ARM hardware.

Changed

Important

  • --dpi is limited to 600 (previously 1200). 600 DPI is more than enough for printed thumbnails, and an A3 page at 1200 DPI needs over 800 MB of memory for a single page.
  • Large PDF output is now split by default at 500 MB. Use --max-pdf-size to raise the limit if you need a single larger file.

Downloads

Platform Archive
macOS (Apple silicon) contact-sheet-v0.5.0-aarch64-apple-darwin.tar.gz
Linux (x86_64, static) contact-sheet-v0.5.0-x86_64-unknown-linux-musl.tar.gz
Linux (ARM64, static) contact-sheet-v0.5.0-aarch64-unknown-linux-musl.tar.gz
Windows (x86_64) contact-sheet-v0.5.0-x86_64-pc-windows-msvc.zip

Each archive has a .sha256 checksum next to it. On macOS, run xattr -d com.apple.quarantine contact-sheet before the first start.

Full Changelog: v0.4.0...v0.5.0

v0.4.0

Choose a tag to compare

@github-actions github-actions released this 25 Oct 22:52

Adds print-ready PDF output (PDF/X-4) and thumbnail-only sheets.

New

PDF/X-4 output – --format pdf

All pages go into a single contact.pdf instead of one JPEG per page. JPEG stays the default.

  • Follows the PDF/X-4 standard (ISO 15930-7) used by print shops: exact A4 pages (MediaBox and TrimBox), required metadata (XMP, dates, trapping status) and an embedded output intent
  • Images are tagged with an embedded sRGB profile and stay in RGB; the print shop's RIP converts them to CMYK, as is usual for PDF/X-4
  • The output intent is FOGRA39 (ISO Coated v2, offset printing on coated paper) by default
contact-sheet --format pdf ~/Pictures/2026-10-17

Your print shop's ICC profile – --output-profile FILE

Prepares the PDF for another printing condition, e.g. FOGRA51 (PSO Coated v3) or uncoated paper. The profile is checked before any photo is loaded: it must be an output (printer) profile in CMYK, RGB or Gray, ICC version 2 or 4. Display profiles such as sRGB or Adobe RGB are rejected with a clear message.

Thumbnails only – --no-labels

Leaves out the file names below the thumbnails. Rows move closer together, so more of them fit on a page. Works with JPEG and PDF output.

Changed

  • No more prebuilt binary for Intel Macs. Downloads are provided for macOS on Apple silicon, Linux and Windows. Intel Mac users can build from source (cargo build --release) or use v0.3.1.

Notes

  • The embedded ICC profiles are public domain (CC0): FOGRA39L from colord and sRGB from Compact ICC Profiles.
  • The files have not been checked with a commercial preflight tool yet. If your print shop's preflight reports a problem, please open an issue.

Downloads

Platform Archive
macOS (Apple silicon) contact-sheet-v0.4.0-aarch64-apple-darwin.tar.gz
Linux (x86_64, static) contact-sheet-v0.4.0-x86_64-unknown-linux-musl.tar.gz
Windows (x86_64) contact-sheet-v0.4.0-x86_64-pc-windows-msvc.zip

Each archive has a .sha256 checksum next to it. On macOS, run xattr -d com.apple.quarantine contact-sheet before the first start.

Full Changelog: v0.3.1...v0.4.0

v0.3.1

Choose a tag to compare

@github-actions github-actions released this 25 Oct 22:11

Adds support for Hasselblad RAW files, including digital backs and models that rawler does not recognise.

New

  • Hasselblad RAW: 3FR and FFF
  • --verbose shows them as "Hasselblad RAW (3FR)" and "Hasselblad RAW (FFF)"

Fixed (compared with the withdrawn v0.3.0)

  • Files from "unknown" Hasselblad cameras are no longer skipped. rawler identifies Hasselblad cameras by their exact model name, but CFV digital backs write the camera body or shutter mode into it (for example Hasselblad CFV-50c/500 or CFV 100C/Electronic Shutter), and some models such as the CFV-50c are missing from rawler altogether. v0.3.0 skipped such files with an "Unknown camera" warning.
  • They are now shown using the preview stored in the file – uncompressed RGB on older cameras, a full-size JPEG on the X2D, CFV 100C and newer models. --verbose prints ? instead of their pixel dimensions, as their sensor data cannot be decoded.

Tested with files from the H3D, H5D-50c, X1D, X2D 100C, CFV-50, CFV-50c and CFV 100C.

Downloads

Platform Archive
macOS (Apple silicon) contact-sheet-v0.3.1-aarch64-apple-darwin.tar.gz
macOS (Intel) contact-sheet-v0.3.1-x86_64-apple-darwin.tar.gz
Linux (x86_64, static) contact-sheet-v0.3.1-x86_64-unknown-linux-musl.tar.gz
Windows (x86_64) contact-sheet-v0.3.1-x86_64-pc-windows-msvc.zip

Note

This is the last release with a prebuilt binary for Intel Macs. Later versions can still be built from source on Intel Macs.

Each archive has a .sha256 checksum next to it. On macOS, run xattr -d com.apple.quarantine contact-sheet before the first start.

Full Changelog: v0.2.0...v0.3.1

v0.2.0

Choose a tag to compare

@github-actions github-actions released this 25 Oct 09:17

Adds support for Nikon and Fujifilm RAW files.

New

  • Nikon RAW: NEF and NRW
  • Fujifilm RAW: RAF, from both Bayer and X-Trans sensors
  • --verbose shows them as "Nikon RAW (NEF)", "Nikon RAW (NRW)" and "Fujifilm RAW (RAF)"

Known limitations

  • Nikon High Efficiency NEF files (Z 8, Z 9 and similar) are shown using their embedded preview, which is fine for contact sheets. rawler cannot decode their sensor data yet, so --verbose prints ? instead of their pixel dimensions.

Downloads

Platform Archive
macOS (Apple silicon) contact-sheet-v0.2.0-aarch64-apple-darwin.tar.gz
macOS (Intel) contact-sheet-v0.2.0-x86_64-apple-darwin.tar.gz
Linux (x86_64, static) contact-sheet-v0.2.0-x86_64-unknown-linux-musl.tar.gz
Windows (x86_64) contact-sheet-v0.2.0-x86_64-pc-windows-msvc.zip

Each archive has a .sha256 checksum next to it. On macOS, run xattr -d com.apple.quarantine contact-sheet before the first start (the binary is not signed).

Full Changelog: v0.1.0...v0.2.0

v0.1.0

Choose a tag to compare

@github-actions github-actions released this 25 Oct 08:48

First release of contact-sheet, a command-line tool that turns a folder of photos into print-ready A4 contact sheets – no import into a photo-management application needed.

Features

  • Formats: Canon RAW (CR2, CR3, CRW), DNG, TIFF (8/16-bit, uncompressed, LZW, Deflate) and JPEG
  • Fast RAW handling: the embedded JPEG preview is used when it is large enough; otherwise the sensor data is developed with rawler
  • Layout: A4 pages with 5 columns of thumbnails (--columns 1–20), each labelled with its file name (long names are shortened in the middle); row height adapts to the tallest photo, and a new page starts when the next row does not fit
  • EXIF orientation is applied; photos are sorted by name
  • Output: contact01.jpg, contact02.jpg, … in a contact-sheet subfolder, at 300 DPI by default (--dpi, 2480×3508 px) with the resolution stored in the JPEG; --quality sets the JPEG quality
  • --landscape page orientation
  • --background white for printing or black for viewing on screen
  • --verbose lists every file with its size, pixel dimensions and format
  • Unreadable files are skipped with a warning; sheets from a previous run are replaced
contact-sheet ~/Pictures/2026-10-17
contact-sheet --background black --columns 4 ~/Pictures/2026-10-17

Downloads

Platform Archive
macOS (Apple silicon) contact-sheet-v0.1.0-aarch64-apple-darwin.tar.gz
macOS (Intel) contact-sheet-v0.1.0-x86_64-apple-darwin.tar.gz
Linux (x86_64, static – any distribution) contact-sheet-v0.1.0-x86_64-unknown-linux-musl.tar.gz
Windows (x86_64) contact-sheet-v0.1.0-x86_64-pc-windows-msvc.zip

Each archive has a .sha256 checksum next to it. The macOS binaries are not signed; to allow them to run, remove the quarantine attribute:

xattr -d com.apple.quarantine contact-sheet

Full Changelog: https://github.com/msetkowski/contact-sheet/commits/v0.1.0