Repository navigation
Releases: msetkowski/contact-sheet
Release list
v0.7.0
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-042The 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.txtno 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
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 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: thecontact-sheetsubfolder, 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.jpgis a sheet butIMG_0007.jpgis 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.jpgwhen 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
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 --recursiveAccepted 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 namedcontact.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
--dpiis 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-sizeto 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
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-17Your 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
Adds support for Hasselblad RAW files, including digital backs and models that rawler does not recognise.
New
- Hasselblad RAW: 3FR and FFF
--verboseshows 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/500orCFV 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.
--verboseprints?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
Adds support for Nikon and Fujifilm RAW files.
New
- Nikon RAW: NEF and NRW
- Fujifilm RAW: RAF, from both Bayer and X-Trans sensors
--verboseshows 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
--verboseprints?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
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 acontact-sheetsubfolder, at 300 DPI by default (--dpi, 2480×3508 px) with the resolution stored in the JPEG;--qualitysets the JPEG quality --landscapepage orientation--background whitefor printing orblackfor viewing on screen--verboselists 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-17Downloads
| 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-sheetFull Changelog: https://github.com/msetkowski/contact-sheet/commits/v0.1.0