Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

fotobuch

fotobuch turns a folder of photos into a print-ready PDF photobook — no cropping, no distortion, no manual drag-and-drop.

You point it at your photos, it figures out the layout: aspect ratios stay intact, reading order flows naturally from top-left to bottom-right, and photos from the same event stay together. Fully automatic, or tweak any page by hand.

Where to goWhat you’ll find
InstallationBinaries, building from source, editor setup
Core ConceptsHow projects, groups, and layouts work
Your First BookZero to PDF in 10 minutes
CommandsEvery command at a glance
PrintingExporting for Saal Digital & friends
Full Flag ReferenceEvery flag, auto-generated from source

Example project

The repository ships with a complete example in docs/examples/ — a ready-made project with sample images and both preview and release PDFs. A good way to explore the YAML, the Typst template, and the output without creating your own project first.

Source code

github.com/EddyXorb/fotobuch

Installation

Download the latest binary for your platform from the Releases page:

PlatformFile
Linux x86_64fotobuch-linux-x86_64.tar.gz
Windows x86_64fotobuch-windows-x86_64.zip

Extract the archive and place the fotobuch binary somewhere on your PATH.

Verify the install:

fotobuch --version

Build from source

Requirements: Rust (stable) and cmake (needed to build the HiGHS optimizer library).

git clone https://github.com/EddyXorb/fotobuch.git
cd fotobuch
cargo build --release
# binary: ./target/release/fotobuch

fotobuch writes a Typst source file alongside the PDF. For a live preview while you work, install VS Code with the Typst Preview extension. Open the .typ file and the preview updates every time you run fotobuch build.

Alternatively, just keep any PDF viewer open and reload after each build.

Shell completions

fotobuch completions --shell bash   >> ~/.bash_completion
fotobuch completions --shell zsh    >> ~/.zshrc
fotobuch completions --shell fish   > ~/.config/fish/completions/fotobuch.fish
fotobuch completions --shell powershell >> $PROFILE

Core Concepts

Before diving in, here’s how the pieces fit together.

Projects

A fotobuch project is a set of files tracked by Git — a YAML config, a Typst template, and cached images. When you run fotobuch project new, it creates a directory with a Git repo inside.

Multiple projects can live in the same Git repo, each on its own branch (fotobuch/<name>). Switching projects (fotobuch project switch) is a Git checkout under the hood — your working directory swaps to the other project’s state.

Photos and groups

When you fotobuch add /some/folder, all photos in that folder become a group. Groups matter because the solver tries to keep photos from the same group together on the same page (or on neighbouring pages). Think of a group as “photos from one occasion”.

Each subfolder you add is a separate group. With --recursive, every subfolder becomes its own group automatically.

Groups are sorted chronologically — by the date in the folder name if there is one, otherwise by the oldest photo’s timestamp.

Placed vs. unplaced

A photo can be in one of two states:

StateMeaning
unplacedIn the project, but not assigned to any page yet
placedAssigned to a specific slot on a specific page

When you run fotobuch build for the first time, all photos are placed automatically — the solver distributes them across pages.

After that first build, any newly added photos start as unplaced. You need to run fotobuch place to put them into the layout before the next fotobuch build.

fotobuch status always shows how many photos are unplaced.

Pages and slots

The solver arranges photos into a grid-like layout on each page. Each photo occupies a slot — a rectangular area with a specific position and size.

Both pages and slots are numbered from 0:

Page 0          Page 1          Page 2
┌──┬──┬──┐     ┌─────┬──┐     ┌──┬─────┐
│0 │1 │2 │     │  0  │1 │     │0 │     │
├──┴──┼──┤     │     ├──┤     ├──┤  2  │
│  3  │4 │     ├─────┤2 │     │1 │     │
└─────┴──┘     └─────┴──┘     └──┴─────┘

Slots are ordered left-to-right, top-to-bottom (reading order). Use fotobuch status <page> to see the slot numbers for a specific page.

A slot address identifies one or more photos:

AddressMeaning
3All slots on page 3
3:2Slot 2 on page 3
3:2..5Slots 2 through 5 on page 3
3:2..5,7Slots 2–5 and slot 7 on page 3
4+New page inserted after page 4 (move destination only)

Two ways to address photos

fotobuch has two different ways to refer to photos, depending on whether they are placed in the layout or not:

By filename or pattern — used for photos that are not yet placed, or when you want to work with photos regardless of their layout position. Matches against the photo’s source path or XMP metadata.

CommandWhat it does
fotobuch add --filter "pattern"Import only matching photos
fotobuch add --filter-xmp "pattern"Import only photos whose XMP metadata matches
fotobuch place --filter "pattern"Place only matching unplaced photos
fotobuch remove "pattern"Remove matching photos from the project

By slot address — used for photos that are already placed on a page. Each placed photo has a unique address like 3:2 (page 3, slot 2). See Pages and slots below for the full syntax.

CommandWhat it does
fotobuch page move 3:2 to 5Move a placed photo to another page
fotobuch page swap 3:2 5:1Swap two placed photos
fotobuch page weight 3:2 2.0Change a placed photo’s weight
fotobuch page info 3:2Show metadata for a placed photo
fotobuch unplace 3:2Remove a placed photo from its slot

Rule of thumb: use filename patterns when working with unplaced photos (add, place, remove), and use slot addresses when working with placed photos (page move, page swap, page weight, unplace).

Weights

Every photo has an area weight (default: 1.0). The solver uses weights to decide how much space each photo gets relative to its neighbours on the same page.

  • Weight 2.0 → roughly twice the area of a weight-1.0 photo
  • Weight 0.5 → roughly half

Set weights when adding (fotobuch add --weight 3) or per-slot afterward (fotobuch page weight 2:0 3.0).

Cover

If you create a project with --with-cover, page 0 becomes the cover. The cover spans the full width (front + spine + back) and has its own bleed and margin settings. See Known Limitations for current caveats.

Build pipeline

fotobuch add     →  photos enter the project (unplaced)
fotobuch place   →  unplaced photos get assigned to pages
fotobuch build   →  solver optimizes layout, renders preview PDF
fotobuch build release  →  renders final PDF at 300 DPI

The first build implicitly places all photos, so you can skip place on a fresh project.

Every command that changes the layout creates a Git commit automatically. Use fotobuch undo / fotobuch redo to navigate the history, and fotobuch history to see the log.

The .fotobuch cache

fotobuch stores resized preview and final images in a .fotobuch/ directory inside your project. This is purely a cache — you can delete it at any time without losing data. The next build will regenerate whatever it needs.

Your First Book

This walkthrough takes you from zero to a print-ready PDF. Prerequisites: fotobuch installed, a folder of photos on your machine.

Prefer learning by example? Check out the complete project in docs/examples/ — it has sample images, YAML config, template, and generated PDFs.


Step 1 — Create a project

fotobuch project new "Italy-2024" --width 297 --height 210

This creates a directory Italy-2024/ with a Git repo, a YAML config, and a Typst template. The --width and --height values are in millimetres (297 × 210 mm = A4 landscape).

Switch into the project folder:

cd "Italy-2024"

Project names must start with a letter and can only contain letters, digits, or dashes.


Step 2 — Review the configuration

Before adding photos, open Italy-2024.yaml in a text editor and check the most important settings. The file already has sensible defaults, but you should set the page count to match the size of book you want:

config:
  book:
    page_width_mm: 297.0
    page_height_mm: 210.0
    bleed_mm: 3.0        # required by most print services
    margin_mm: 0.0       # 0 = edge-to-edge; set to e.g. 10 for a white border
    gap_mm: 5.0          # the gap between the photos in your layout; if set to 0, photos touch each other
  book_layout_solver:
    page_target: 20      # how many pages you want
    page_max: 24         # upper limit — give the solver some room above the target

The book layout solver is the algorithm that decides how your photos are distributed across pages. page_target is your desired page count; page_max is the hard upper limit. Setting page_max a few pages above page_target gives the solver freedom to use an extra page when that produces a significantly better layout.

If you plan to use a cover, also set:

    cover:
      active: true
      front_back_width_mm: 594.0   # total width of front + back panel
      height_mm: 297.0
      spine_text: "Italy 2024"     # or ~ for no text
      spine_mm: 15.0               # the spine width of your book - does not add to front_back_width_mm, when spine_mode = fixed
      spine_mode: fixed            # fixed = spine_mm is the exact width; auto = spine width is determined by page count

Other settings you might want to adjust: search_timeout (solver time limit for large books).

See Configuration for a full reference of all settings.

Tip: You can always change these later and re-run fotobuch build. The solver will redistribute photos according to the new settings.


Step 3 — Add photos

Point fotobuch at one or more folders. Each folder becomes a group — photos from the same group are kept together on pages, but neighboured groups can mix.

fotobuch add /photos/2024-07-Italy
fotobuch add /photos/2024-08-Hiking

Folders with a date in the name (2024-07-Italy, 20240715_Rome) are sorted chronologically. Folders without a recognisable date are sorted by the oldest photo’s timestamp.

You can also add single files, add recursively (each subfolder = its own group), or filter by file name or xmp-data (that is where your rating and other metadata normally sits for each photo, when you use e.g. lightroom):

# single file
fotobuch add /photos/2024-07-Italy/DSC_0042.jpg

# recursive — each subfolder becomes a group
fotobuch add --recursive /photos/2024-summer

# only photos matching a filename pattern
fotobuch add /photos/2024-07-Italy --filter "DSC_00.*\.jpg"

# only 3-to-5-star photos, giving them more space
fotobuch add /photos/2024-07-Italy --filter-xmp "Rating.*[3-5]" --weight 5

Check what was imported:

fotobuch status

Step 4 — Build a preview

fotobuch build

On the first run, fotobuch distributes all photos across pages automatically and renders a preview PDF at lower DPI (fast). Open Italy-2024.pdf to review the result — or open Italy-2024.typ in VS Code with Typst Preview for a live preview.


Step 5 — Adjust the layout

You will almost certainly want to tweak a few things. The general procedure is to make a change to the layout using the page subcommands move, swap, combine, split. After applying one of these (or multiple of these if you want), run fotobuch build to finally apply all the changes to the layout you made.

Move a photo to another page:

fotobuch page move 3:2 to 5

This moves slot 2 on page 3 to page 5. (Pages and slots count from 0 — use fotobuch status 3 to see which slot is which.)

Swap two photos

fotobuch page swap 2:3 2:7

Swap two pages:

fotobuch page swap 3 7

Split a page into two

fotobuch page split 3:2

Creates a new page after page 3 and moves slot 2 (and all photos after it) to the new page.

Combine two pages

fotobuch page combine 3..7

Moves all photos from pages 4 to 7 to the end of page 3, then deletes pages 4 to 7. You can also write fotobuch page combine 3,4,5,6,7 for the same effect.

Give a photo more space in the next build (weight > 1 = relatively larger):

fotobuch page weight 3:2 2.0

Re-solve a single page (runs the solver again from scratch for that page):

fotobuch rebuild --page 6

Undo any change:

fotobuch undo

Every change is committed to Git automatically — fotobuch history shows the log, and fotobuch undo N rolls back N steps.

Rebuild the preview after changes:

fotobuch build

Iterate through the above steps until you are happy with the result.


Step 6 — Adding more photos later

After the first build, newly added photos start as unplaced. Place them before building:

fotobuch add /photos/bonus-shots
fotobuch place
fotobuch build

This will distribute the unplaced photos on matching pages; no new page will be added though. A page is matching for a new photo if the photos timestamp fits into the timestamps of the already existing pages.

You can also place all unplaced photos onto a specific page:

fotobuch place --into 4

or combine it with a filter for filenames:

fotobuch place --filter "DSC_00.*\.jpg" --into 6

Step 7 — Export for print

When you’re happy with the layout:

fotobuch build release

This re-renders all images at 300 DPI and writes Italy-2024_final.pdf. The file is ready to upload to your print service (e.g. Saal Digital).

See Printing for Saal Digital-specific details.

Configuration

Every project has a {project-name}.yaml file that controls the entire book. You don’t need to write this file from scratch — fotobuch project new creates it with sensible defaults. You only edit the parts you want to change.

Run fotobuch config show at any time to see the full resolved configuration (including all defaults). Use fotobuch config set <key> <value> to change a value without editing the YAML directly — see the Full Flag Reference for exact syntax.

For a quick overview of the most important settings to check before your first build, see Step 2 in Your First Book.

Tip: If the solver produces poor results, the most common fixes are: increase search_timeout (more time), or disable enable_local_search. You may also want to tweak the solver weights according to your needs (e.g. increase weight_split if you want groups to be respected more strictly).


Full reference

The YAML has this structure:

config:
  book:                  # page dimensions, margins, bleed, cover
    cover:               # cover-specific settings (nested inside book)
  book_layout_solver:    # photo-to-page distribution
  page_layout_solver:    # single-page layout (genetic algorithm)
    weights:             # fitness function weights (nested)
  preview:               # preview rendering options

All fields below are optional unless marked otherwise. Defaults are applied automatically for any field you don’t set.


config.book — Page dimensions and layout

FieldDefaultDescription
title"Untitled"Book title. Used as the default spine text on the cover.
page_width_mm210.0Page width in mm. Set at project creation with --width. For double-page spreads, use the combined width (e.g. 420).
page_height_mm297.0Page height in mm. Set at project creation with --height.
bleed_mm3.0Bleed area in mm added around each page. Cut off by the printer. Most services require 3 mm.
margin_mm0.0Minimum inset from the page edge. 0 = edge-to-edge (photos may bleed). > 0 = white border (bleed extension is disabled).
gap_mm5.0Space in mm between photos on the same page.
bleed_threshold_mm3.0Only active when margin_mm is 0. If a photo’s edge is closer to the page edge than this value, the layout is scaled so the photo extends fully into the bleed area. Prevents thin white strips at the page edge after cutting.
dpi300.0DPI for the final release PDF. Controls the resolution of cached images used in fotobuch build release.

config.book.cover — Cover page

All cover fields are optional. The cover is inactive by default. Enable it by setting active: true and providing dimensions.

FieldDefaultDescription
activefalseEnable the cover. When true, the first layout entry (page 0) becomes the cover page.
front_back_width_mm0.0Total width of front + back panel combined, without the spine. Required when active: true.
height_mm0.0Cover height in mm. Required when active: true.
modesplitCover layout mode. Controls how page 0 is solved. split = deterministic solver optimises the cover (default). See Cover modes below.
spine_clearance_mm5.0Gap in mm between the photo edge and the spine for front, back, and split modes. Ignored for spread modes.
spine_textbook titleText on the spine. Set to ~ (null) for no text. Font size is auto-calculated from the spine width (max 80% of spine width).
spine_modeautoSpine width mode — see below.
spine_mm_per_10_pages1.4Auto mode only. Spine thickness per 10 inner pages. Spine width = (inner_pages / 10) * spine_mm_per_10_pages. In auto mode the spine width affects the total cover canvas width that the solver uses.
spine_width_mmFixed mode only. A fixed spine width in mm. In fixed mode the spine does not affect the cover canvas width in the solver — it is only used by the template for display and text sizing.
bleed_mm3.0Bleed for the cover page (independent from inner-page bleed).
margin_mm0.0Margin for the cover page. Same behaviour as the inner-page margin.
gap_mm5.0Gap between photos on the cover.
bleed_threshold_mm3.0Bleed threshold for the cover. Same behaviour as inner pages.

Cover modes

When mode is not free, the GA solver is bypassed and slot positions are calculated deterministically from the cover geometry. A warning is printed if the number of photos on the cover does not match what the mode expects.

See Cover Modes — Visual Guide for visual examples of each mode.

ModePhotosBehaviour
freeanyGA solver optimises freely (default)
front1Photo on the front panel, aspect ratio preserved and centred
front-full1Photo fills the entire front panel (may crop)
back1Photo on the back panel, aspect ratio preserved and centred
back-full1Photo fills the entire back panel (may crop)
spread1Photo spans the full spread (over spine), aspect ratio preserved and centred
spread-full1Photo fills the full spread (may crop)
split2Slot 0 → front, slot 1 → back, aspect ratio preserved and centred
split-full2Slot 0 → front, slot 1 → back, each half fully filled (may crop)

Workflow example — single photo on the front:

# 1. Place the photo onto the cover
fotobuch place cover.jpg --into 0

# 2. Set the mode in the YAML
#    cover:
#      mode: front

# 3. Rebuild only the cover
fotobuch rebuild --page 0

Workflow example — panorama across full spread:

fotobuch place panorama.jpg --into 0
# cover: { mode: spread-full }
fotobuch rebuild --page 0

Spine modes explained:

  • auto (default): Spine width is calculated from the number of inner pages. The formula is spine_width = (inner_pages / 10) * spine_mm_per_10_pages. The spine width is added to front_back_width_mm to form the total cover canvas — meaning the solver accounts for the spine. Use this when your print service calculates spine from page count (most common).

  • fixed: You provide spine_width_mm directly. The spine is not added to the canvas width — the solver uses front_back_width_mm as-is. The fixed spine width is only used by the template for positioning the spine text. Use this when you already know the exact spine width from your print service.


config.book_layout_solver — Photo-to-page distribution

The book layout solver distributes your photos across pages. It first runs a Mixed Integer Program (MIP) to find a globally optimal assignment, then refines it with a local search that evaluates actual layout quality per page.

FieldDefaultDescription
page_target12Target number of pages. The solver tries to hit this count. This is the most important solver setting.
page_min1Hard minimum number of pages.
page_max26Hard maximum number of pages. Setting this above page_target gives the solver room to add pages when that improves layout quality.
photos_per_page_min1Minimum number of photos on any single page.
photos_per_page_max20Maximum number of photos on any single page.
group_max_per_page5Maximum number of different groups that may share a single page. Lower values keep groups more separated.
group_min_photos1When a group is split across two pages, each part must have at least this many photos. Prevents a single “orphan” photo appearing alone on the next page.
weight_even1.0MIP objective weight for even photo distribution across pages. Higher = more uniform page fill.
weight_split10.0MIP objective weight penalising group splits. Higher = groups are less likely to be split across pages.
weight_pages5.0MIP objective weight penalising deviation from page_target. Higher = result stays closer to the target.
search_timeout30sTime budget for the entire solver (MIP + local search). Increase for large books. YAML format: {secs: 60, nanos: 0}.
enable_local_searchtrueWhether to run the local search after the MIP. The local search shifts page boundaries to improve per-page layout quality.
mip_rel_gap0.01Relative optimality gap for the MIP solver (0.0 = exact, 0.01 = accept solutions within 1% of optimal). Tightening this rarely helps and increases solve time.
max_photos_for_split300When the total photo count exceeds this, the problem is automatically decomposed into smaller sub-problems solved sequentially. This avoids MIP timeouts on very large books.
split_group_boundary_slack5When splitting into sub-problems, the split point may deviate by this many photos from the ideal boundary to prefer splitting at a group boundary.
max_coverage_cost0.95(Currently unused — will be removed in a future version.) Was intended as a threshold for the local search to identify “bad” pages, but is not read by the solver.

config.page_layout_solver — Single-page layout (genetic algorithm)

The page layout solver arranges photos within a single page using a genetic algorithm with island-model parallelism. These are advanced tuning parameters — the defaults work well for most cases.

FieldDefaultDescription
seed42Random seed for the genetic algorithm. Change this to get a different layout for the same input. fotobuch rebuild changes the seed automatically.
population_size750Number of individuals (candidate layouts) per island. Larger = better results but slower.
max_generations100Maximum number of generations the algorithm runs.
mutation_rate0.3Probability that an individual is mutated per generation.
crossover_rate0.7Probability that two individuals are recombined per generation.
elite_count20Number of best individuals carried over unchanged to the next generation.
no_improvement_limit15Stop early if no improvement is found for this many generations. Set to ~ (null) to disable early stopping.
enforce_ordertrueEnforce chronological reading order (top-left to bottom-right) on each page. When true, photos are arranged so earlier photos appear before later ones in natural reading direction. Set to false if you don’t care about photo order and want the solver to optimise purely for visual quality — this often produces tighter layouts.
islands_nrCPU coresNumber of independent populations evolved in parallel. Defaults to the number of available CPU cores.
islands_migration_interval5Generations between migration events (best individuals are copied between islands).
islands_nr_migrants2Number of individuals migrated per island per migration event.

config.page_layout_solver.weights — Fitness function

The fitness function evaluates how good a single-page layout is. It combines three cost components, each multiplied by its weight. Lower cost = better layout.

FieldDefaultDescription
w_coverage1.0Weight for canvas coverage cost. Penalises unused white space on the page. This is the dominant term.
w_size0.2Weight for size distribution cost. Penalises photos that deviate from their target size (determined by their area_weight).
w_barycenter0.0Weight for barycenter centering cost. Penalises layouts whose visual centre of mass is far from the page centre. Disabled by default (0.0).

config.preview — Preview rendering

All preview overlay settings are automatically suppressed in build release.

FieldDefaultDescription
show_filenamesfalseShow the photo filename as a caption on each photo. Useful for identifying photos when adjusting the layout.
max_preview_px800Maximum pixel size (longest edge) of cached preview images. Lower = faster builds, less disk space, blurrier preview.
show_borderstrueShow red bleed border and blue margin border overlays on each page.
show_slot_infotrueShow slot address and area weight on each photo (e.g. 3:2 (1.5)).

config.book.appendix — Photo index

The appendix is a compact photo index appended at the end of both the preview and release PDFs, listing every photo with its group, timestamp, and a page-position reference.

FieldDefaultDescription
activefalseEnable the photo index.
columns7Number of columns in the listing.
ref_mode"positions"Reference style: "positions" (page.slot, e.g. 2.3) or "counter" (sequential number badge on each photo).
page_separatorfalseShow a page-number header between pages in the listing.
strip_timestampstrueStrip leading ISO timestamps from filenames in the listing.
label_title"Photo Index"Title text of the appendix.
label_page"Page"“Page” label used in the cross-reference legend and page separators.
date_format"{day}. {month} {year} {hour}:{min}"Format string for timestamps. Placeholders: {day}, {month}, {year}, {hour}, {min}.
date_months["Jan", …, "Dec"]Month abbreviations (12 entries, January–December).

Example: a typical YAML

config:
  book:
    title: "Italy 2024"
    page_width_mm: 420.0    # double-page spread
    page_height_mm: 297.0
    bleed_mm: 3.0
    margin_mm: 0.0
    gap_mm: 5.0
    cover:
      active: true
      front_back_width_mm: 594.0
      height_mm: 297.0
      spine_mode: auto
      spine_mm_per_10_pages: 1.4
      spine_text: "Italy 2024"
    appendix:
      active: true
      label_title: "Photo Index"
      label_page: "Page"
  book_layout_solver:
    page_target: 20
    page_max: 24
    photos_per_page_max: 8
    search_timeout:
      secs: 60
      nanos: 0
  preview:
    show_filenames: true
    show_borders: true
    show_slot_info: true
    max_preview_px: 800

Cover Modes

Each cover mode determines how photos are positioned and sized on the cover. The examples below show the result of each mode with a sample photo.


Mode: front

A single photo on the front panel, with its aspect ratio preserved and centred.

front mode example


Mode: front-full

A single photo fills the entire front panel (may crop to fit).

front-full mode example


Mode: back

A single photo on the back panel, with its aspect ratio preserved and centred.

back mode example


Mode: back-full

A single photo fills the entire back panel (may crop to fit).

back-full mode example


Mode: spread

A single photo spans the full spread (front, spine, and back), with its aspect ratio preserved and centred.

spread mode example


Mode: spread-full

A single photo fills the full spread without cropping space for the spine (may crop the photo).

spread-full mode example


Mode: split

Two photos: slot 0 goes on the front panel, slot 1 on the back panel. Both have their aspect ratios preserved and are centred.

split mode example


Mode: split-full

Two photos: slot 0 fills the front panel, slot 1 fills the back panel (each may crop independently).

split-full mode example


Mode: free

The genetic algorithm solver optimises photo placement freely without constraints. Use any number of photos.

free mode example

Command Overview

All commands follow the pattern fotobuch <command> [options]. Run fotobuch --help or fotobuch <command> --help for details, or see the Full Flag Reference.

Your original photos are never modified. fotobuch only reads your source files to create cached copies at the configured DPI. Commands like remove delete photos from the project YAML — your originals on disk are untouched.

Commands at a glance

CommandWhat it does
project newCreate a new photobook project
project listList all projects in the current repo
project switchSwitch to another project (checks out its Git branch)
addImport photos or folders into the project
removeDelete photos from the project entirely
placeAssign unplaced photos to pages
unplaceRemove photos from their page slots (they stay in the project)
buildSolve layout and render preview PDF
build releaseRender final PDF at full resolution (300 DPI)
rebuildRe-run the solver on specific pages
page moveMove photos between pages
page swapSwap pages or slots
page splitSplit a page at a slot
page combineMerge pages together
page infoShow photo metadata for slots on a page
page weightSet the area weight for one or more slots
page modeToggle a page between auto (solver) and manual placement
page posMove or scale slots on a manual-mode page
statusShow project overview (or single-page detail)
config showPrint the resolved configuration with all defaults
config setSet a config value using dot-notation (e.g. book.dpi 150)
historyShow the project change log
undoUndo the last N changes
redoRedo N undone changes

For all flags and exact syntax see the Full Flag Reference. For available config keys see Configuration.

remove vs. unplace

  • remove deletes photos from the project. They are gone (unless you undo).
  • unplace takes photos off their page but keeps them in the project. They become unplaced and can be re-placed with fotobuch place.

Use remove --keep-files if you want remove-like pattern matching but unplace-like behaviour (photos stay, just lose their page assignment).

build vs. rebuild

  • build renders the PDF and only re-solves pages that changed since the last build. On the first run it solves everything.
  • rebuild --page N forces the solver to re-optimize page N from scratch, even if nothing changed. Useful when you’re not happy with a layout.
  • rebuild --all re-solves every page.

Customizing the Template

Every project has a {name}.typ file — a Typst template that controls how the PDF looks. ^fotobuch generates this file for you, but you are free (and encouraged) to edit it, if you are proficient with typst. Tweak it to your needs, use it as a starting point. fotobuch will not overwrite your template once you created it, so your changes stay safe within the project folder and can also be reused in other fotobuch projects. Make sure that during your edits you do not change the lines

#let is_final = false
#let project_name = "{project_name}"
#let data = yaml(project_name + ".yaml")
#set text(font: "Libertinus Serif")

otherwise it won’t compile.

Important: Always edit {name}.typ, never final.typ. The final template is auto-generated from yours during build release (with is_final = true). Your changes in final.typ would be overwritten.


Preview overlays

These settings only affect the preview PDF. They are automatically disabled in the release build. Configure them in {name}.yaml:

config:
  preview:
    show_filenames: false    # show filename caption on each photo
    show_borders: true       # red bleed border + blue margin border
    show_slot_info: true     # slot address and weight on each photo

Turn on show_filenames when you’re trying to identify which photo is where. Turn off show_slot_info once you’re happy with the layout and just want a clean preview.


Photo index (appendix)

The template can append a photo index at the end of the book — a compact reference listing every photo with its group, timestamp, and a reference back to its page position. Configure it under config.book.appendix in {name}.yaml:

config:
  book:
    appendix:
      active: true
      columns: 7
      ref_mode: "positions"   # or "counter"
      label_title: "Photo Index"
      label_page: "Page"
SettingDefaultEffect
activefalseEnable the appendix
columns7Number of columns in the listing
ref_mode"positions"How photos are referenced (see below)
page_separatorfalseShow a page-number header between pages
strip_timestampstrueTry to strip leading timestamps from filenames
label_title"Photo Index"Localization: Title text
label_page"Page"Localization: “Page” label
date_format"{day}. {month} {year} {hour}:{min}"Timestamp format
date_months["Jan", …, "Dec"]Month abbreviations

Reference modes

"positions" (default) — Each photo is referenced as page.slot, e.g. 2.3 means page 2, slot 3. No visual badge is added to the photos.

"counter" — Photos are numbered sequentially (1, 2, 3, …) and a small badge with the number appears in the bottom-right corner of each photo in the PDF.


Going further

The template reads layout data from {name}.yaml via #let data = yaml(…).

A few things to keep in mind:

  • is_final controls preview vs. release mode. Use it to conditionally show or hide elements: #if not is_final [Draft watermark].
  • Image paths are resolved relative to the project root via cache_prefix. Preview images live in .fotobuch/cache/{name}/preview/, final images in .fotobuch/cache/{name}/final/.

If you want to start over with the default template, create a fresh project with fotobuch project new and copy the generated {name}.typ back.

Printing & Export

General checklist

Before uploading your PDF, verify these things:

  • Run fotobuch build release — only the release PDF has full 300 DPI, do NOT UPLOAD THE PREVIEW PDF to be printed, it will be ugly!
  • Check the terminal output for DPI warnings (photos that are too small for their slot will be listed)
  • Open the final PDF ({name}_final.pdf) and spot-check a few pages

Saal Digital

fotobuch generates PDFs that meet Saal Digital’s technical requirements out of the box:

  • Bleed: 3 mm on all sides (configurable via config.book.bleed_mm in the YAML)
  • PDF boxes: MediaBox, TrimBox, and BleedBox are set correctly — matching what InDesign would produce
  • Resolution: 300 DPI for build release (configurable via config.book.dpi)

Other print services

Most print-on-demand services accept standard PDF with bleed. Adjust config.book.bleed_mm if your provider requires a different bleed size. The default 3 mm works for the majority of European providers.

Known Limitations

There are surely plenty of limitations I am not aware of at the moment, but let’s start with that:

  • photo weights are not respected cross-page-wise - will be adressed in future
  • No GUI so far for user not used to a cli - will be adressed in future

What fotobuch deliberately does not do

  • No pixel-level placement. The solver decides where photos go; you influence it through weights, groups, and rebuild commands.
  • No mixed page sizes within one project.
  • No image editing (cropping, colour correction, rotation). Prepare your photos beforehand.

Full Flag Reference

This page is auto-generated from the CLI source. Run cargo run --example generate-cli-docs to regenerate.

Command-Line Help for fotobuch

This document contains the help content for the fotobuch command-line program.

Command Overview:

fotobuch

Photobook layout solver and project manager

Usage: fotobuch <COMMAND>

Subcommands:
  • add — Add photos to the project
  • build — Calculate layout and generate preview or final PDF
  • rebuild — Force re-optimization of pages or page ranges
  • place — Place unplaced photos into the book
  • unplace — Remove photos from the layout at a page:slot address (they stay in the project)
  • page — Page manipulation commands (move, split, combine, swap)
  • remove — Remove photos or groups from the book
  • status — Show project status
  • config — Configuration commands (show or mutate)
  • history — Show project change history
  • undo — Undo the last N commits (default: 1)
  • redo — Redo N previously undone commits (default: 1)
  • project — Project management commands
  • init — Create a new photobook project (alias for project new)
  • completions — Print shell completion script to stdout

fotobuch add

Add photos to the project

Usage: fotobuch add [OPTIONS] [PATHS]...

Arguments:
  • <PATHS> — Directories or files containing photos to add
Options:
  • --allow-duplicates — Allow adding duplicate photos (by hash)

  • --filter-xmp <REGEX> — Only include photos whose XMP metadata matches this regex (can be repeated, all must match)

  • --filter <REGEX> — Only include photos whose source path matches this regex pattern (can be repeated, all must match)

  • -d, --dry — Preview what would be added without writing anything

  • --update — Re-add photos whose path already exists but whose content has changed

  • -r, --recursive — Scan directories recursively (each subdir becomes its own group)

  • --weight <WEIGHT> — Area weight for all imported photos (default: 1.0)

    Default value: 1

fotobuch build

Calculate layout and generate preview or final PDF

Usage: fotobuch build [OPTIONS] [COMMAND]

Subcommands:
  • release — Generate final high-quality PDF at 300 DPI
Options:
  • --pages <PAGES> — Only rebuild specific pages (0-based, comma-separated or repeated flag)

fotobuch build release

Generate final high-quality PDF at 300 DPI

Usage: fotobuch build release [OPTIONS]

Options:
  • --force — Force release even if layout has uncommitted changes

fotobuch rebuild

Force re-optimization of pages or page ranges

Usage: fotobuch rebuild [OPTIONS]

Options:
  • --page <PAGE> — Single page to rebuild (0-based index)

  • --range-start <RANGE_START> — Start of page range (0-based index, requires –range-end)

  • --range-end <RANGE_END> — End of page range (0-based index, inclusive, requires –range-start)

  • --flex <FLEX> — Allow page count to vary by +/- N (only with range)

    Default value: 0

  • --all — Rebuild all pages from scratch

fotobuch place

Place unplaced photos into the book

Usage: fotobuch place [OPTIONS]

Options:
  • --filter <REGEX> — Only place photos matching this regex pattern (can be repeated, all must match)
  • --into <INTO> — Place all matching photos onto this specific page (0-based index)

fotobuch unplace

Remove photos from the layout at a page:slot address (they stay in the project)

The page is deleted automatically if it becomes empty.

Usage: fotobuch unplace <ADDRESS>

Arguments:
  • <ADDRESS> — Slot address: “3:2” (slot 2 on page 3), “3:2,7”, “3:2..5”, “3:2..5,7”

fotobuch page

Page manipulation commands (move, split, combine, swap)

Usage: fotobuch page <COMMAND>

Subcommands:
  • move — Move or unplace photos between pages
  • split — Split a page at a slot: photos from that slot onwards move to a new page inserted after
  • combine — Merge pages onto the first one, then delete the now-empty source pages
  • swap — Swap photos between two addresses (only single numbers or ranges, no comma lists)
  • info — Show photo metadata for slots on a page
  • weight — Set area_weight for one or more slots
  • mode — Toggle page mode between auto (solver) and manual (user-placed)
  • pos — Reposition or rescale slots on a Manual-mode page

fotobuch page move

Move or unplace photos between pages

Two forms: “SRC to DST” (move) and “SRC out” (unplace).

Addressing: 3 = whole page, 3:2 = slot 2 on page 3, 3:1..3,7 = slots 1-3 and 7, 4+ = new page after 4.

Move examples: “3:2 to 5”, “3,4 to 5”, “3:2 to 4+”. Unplace examples: “3 out”, “3:2 out”.

See the documentation for the full addressing syntax.

Usage: fotobuch page move [ARGS]...

Arguments:
  • <ARGS> — Expression passed as space-separated tokens, e.g.: 3:2 to 5

fotobuch page split

Split a page at a slot: photos from that slot onwards move to a new page inserted after

Shortcut for page move PAGE:SLOT.. to PAGE+. Error if SLOT is the first slot (would leave the original page empty).

Usage: fotobuch page split <ADDRESS>

Arguments:
  • <ADDRESS> — Address “PAGE:SLOT”, e.g. “3:4” splits page 3 at slot 4

fotobuch page combine

Merge pages onto the first one, then delete the now-empty source pages

All following page numbers shift down accordingly.

Usage: fotobuch page combine <PAGES>

Arguments:
  • <PAGES> — Pages expression: “3,5” (page 5 onto 3) or “3..5” (pages 4-5 onto 3)

fotobuch page swap

Swap photos between two addresses (only single numbers or ranges, no comma lists)

Page swap: “3 5” swaps pages, “1..2 5..9” swaps blocks. Slot swap: “3:2 5:6” swaps individual slots, “3:2..4 5:6..9” swaps slot ranges (different sizes ok).

Errors on overlapping ranges or comma-separated lists as operands.

Usage: fotobuch page swap <LEFT> <RIGHT>

Arguments:
  • <LEFT> — Left address: “3:2”, “3:1..3”, “3”, “3..6”
  • <RIGHT> — Right address: “5:6”, “5:2..4”, “5”, “8..11”

fotobuch page info

Show photo metadata for slots on a page

Address forms: 3 (all slots), 3:2 (single slot), 3:1..3,7 (slots 1–3 and 7).

Without flags: full table (or vertical view for a single slot). With a flag: machine-readable single-field output.

Usage: fotobuch page info [OPTIONS] <ADDRESS>

Arguments:
  • <ADDRESS> — Address: “3”, “3:2”, “3:1..3,7”
Options:
  • --weights — Output only area weights (format: page:slot=weight)
  • --ids — Output only photo IDs
  • --pixels — Output only pixel dimensions

fotobuch page weight

Set area_weight for one or more slots

Examples: 3:2 2.0 (single slot), 3:1..3,7 2.0 (multiple slots), 3 2.0 (whole page).

Usage: fotobuch page weight <ADDRESS> <WEIGHT>

Arguments:
  • <ADDRESS> — Address: “3”, “3:2”, “3:1..3,7”
  • <WEIGHT> — Weight value (must be > 0)

fotobuch page mode

Toggle page mode between auto (solver) and manual (user-placed)

Syntax: fotobuch page mode <pages> <a|m|auto|manual>

Examples: 3 m (page 3 to manual), 3..5 a (pages 3-5 to auto).

Usage: fotobuch page mode <PAGES> <MODE>

Arguments:
  • <PAGES> — Pages to change: “3”, “3..5”, “3,5”
  • <MODE> — Mode: ‘a’ or ‘auto’ for auto-solver, ‘m’ or ‘manual’ for manual placement

fotobuch page pos

Reposition or rescale slots on a Manual-mode page.

Syntax: fotobuch page pos <address> [--by dx,dy] [--at x,y] [--scale s]

Examples: 4:2 --by -20,30 — move slot 2 on page 4 relatively 4:2 --at 100,50 — set slot 2 origin to (100mm, 50mm) 4:2 --scale 1.5 — scale slot 2 by 1.5× 4:2..5 --by -20,30 — move slots 2–5 together 4:2 --at 100,50 --scale 2 — absolute position + scale

At least one of –by, –at, –scale is required. –by and –at are mutually exclusive. The page must be in manual mode.

Usage: fotobuch page pos <--by <BY>|--at <AT>|--scale <SCALE>> <ADDRESS>

Arguments:
  • <ADDRESS> — Address: “4:2”, “4:2..5”, “4:1,3”
Options:
  • --by <BY> — Relative move in mm: “dx,dy” (e.g. “-20,30”)
  • --at <AT> — Absolute position in mm: “x,y” (e.g. “100,50”)
  • --scale <SCALE> — Scale factor applied to width and height (origin stays fixed)

fotobuch remove

Remove photos or groups from the book

Usage: fotobuch remove [OPTIONS] [PATTERNS]...

Arguments:
  • <PATTERNS> — Photos, group names, or regex patterns to remove (can be repeated)
Options:
  • --keep-files — Only remove from layout, keep photos in the project (makes them unplaced)
  • --unplaced — Remove all photos that are not placed in any layout page

fotobuch status

Show project status

Usage: fotobuch status [PAGE]

Arguments:
  • <PAGE> — Show detailed information for a specific page (0-based index)

fotobuch config

Configuration commands (show or mutate)

Usage: fotobuch config <COMMAND>

Subcommands:
  • show — Show resolved configuration with defaults
  • set — Set a config value using dot-notation (e.g. book.dpi 300)

fotobuch config show

Show resolved configuration with defaults

Usage: fotobuch config show

fotobuch config set

Set a config value using dot-notation (e.g. book.dpi 300)

Supported keys mirror the YAML config hierarchy. Types are auto-detected: true/false → bool, integers → int, decimals → float, else string.

Usage: fotobuch config set <KEY> <VALUE>

Arguments:
  • <KEY> — Dot-notation key, e.g. “book.dpi” or “book.cover.active”
  • <VALUE> — New value, e.g. “300”, “true”, “3.5”, “spread”

fotobuch history

Show project change history

Usage: fotobuch history [OPTIONS]

Options:
  • -n <COUNT> — Number of entries to show (0 = all)

    Default value: 5

fotobuch undo

Undo the last N commits (default: 1)

Usage: fotobuch undo [STEPS]

Arguments:
  • <STEPS> — Number of steps to undo

    Default value: 1

fotobuch redo

Redo N previously undone commits (default: 1)

Usage: fotobuch redo [STEPS]

Arguments:
  • <STEPS> — Number of steps to redo

    Default value: 1

fotobuch project

Project management commands

Usage: fotobuch project <COMMAND>

Subcommands:
  • new — Create a new photobook project
  • list — List all photobook projects
  • switch — Switch to another photobook project

fotobuch project new

Create a new photobook project

Usage: fotobuch project new [OPTIONS] --width <WIDTH> --height <HEIGHT> <NAME>

Arguments:
  • <NAME> — Project name
Options:
  • --width <WIDTH> — Page width in millimeters

  • --height <HEIGHT> — Page height in millimeters

  • --bleed <BLEED> — Bleed margin in millimeters

    Default value: 3

  • --parent-dir <PARENT_DIR> — Parent directory where project will be created (default: current directory)

  • --quiet — Suppress welcome message

    Default value: false

  • --with-cover — Create project with an active cover page

    Default value: false

  • --cover-width <COVER_WIDTH> — Cover width in millimeters (defaults to page_width * 2 if –with-cover is set, with warning)

  • --cover-height <COVER_HEIGHT> — Cover height in millimeters (defaults to page_height if –with-cover is set, with warning)

  • --spine-grow-per-10-pages-mm <SPINE_GROW_PER_10_PAGES_MM> — Spine width growth per 10 inner pages in mm (auto mode, conflicts with –spine-mm)

  • --spine-mm <SPINE_MM> — Fixed spine width in mm (conflicts with –spine-grow-per-10-pages-mm)

  • --margin-mm <MARGIN_MM> — Inner margin in millimeters (default: 0)

    Default value: 0

fotobuch project list

List all photobook projects

Usage: fotobuch project list

fotobuch project switch

Switch to another photobook project

Usage: fotobuch project switch <NAME>

Arguments:
  • <NAME> — Project name to switch to

fotobuch init

Create a new photobook project (alias for project new)

Usage: fotobuch init [OPTIONS] --width <WIDTH> --height <HEIGHT> <NAME>

Arguments:
  • <NAME> — Project name
Options:
  • --width <WIDTH> — Page width in millimeters

  • --height <HEIGHT> — Page height in millimeters

  • --bleed <BLEED> — Bleed margin in millimeters

    Default value: 3

  • --parent-dir <PARENT_DIR> — Parent directory where project will be created (default: current directory)

  • --quiet — Suppress welcome message

    Default value: false

  • --with-cover — Create project with an active cover page

    Default value: false

  • --cover-width <COVER_WIDTH> — Cover width in millimeters

  • --cover-height <COVER_HEIGHT> — Cover height in millimeters

  • --spine-grow-per-10-pages-mm <SPINE_GROW_PER_10_PAGES_MM> — Spine width growth per 10 inner pages in mm

  • --spine-mm <SPINE_MM> — Fixed spine width in mm

  • --margin-mm <MARGIN_MM> — Inner margin in millimeters (default: 0)

    Default value: 0

fotobuch completions

Print shell completion script to stdout

Usage: fotobuch completions –shell bash >> ~/.bash_completion fotobuch completions –shell zsh >> ~/.zshrc fotobuch completions –shell fish > ~/.config/fish/completions/fotobuch.fish fotobuch completions –shell powershell >> $PROFILE

Usage: fotobuch completions --shell <SHELL>

Options:
  • --shell <SHELL> — Shell to generate completions for

    Possible values: bash, elvish, fish, powershell, zsh


This document was generated automatically by clap-markdown.