Local batch photo-stamping utility. Select a folder of images, derive stamp text from each filename, and save stamped copies — originals are never modified.
Works fully offline. No cloud APIs, no internet required.
- Python 3.10 or later
- macOS or Windows
- Tkinter (included with the standard Python installer on Windows and macOS)
# 1. Create and activate a virtual environment
python3 -m venv .venv
source .venv/bin/activate
# 2. Install dependencies
pip install -r requirements.txt
# 3. Run the app
python app.pyThese steps assume you are using Command Prompt or PowerShell in the project folder.
- Download Python from python.org/downloads.
- Run the installer.
- On the first screen, check “Add python.exe to PATH”.
- Click “Customize installation” and make sure tcl/tk and IDLE is enabled (needed for the GUI).
- Finish the install.
To confirm Python is available, open a new Command Prompt and run:
python --versionYou should see something like Python 3.12.x.
Open Command Prompt in the PhotoStamp project folder:
python -m venv .venv
.venv\Scripts\activateYour prompt should now start with (.venv).
pip install -r requirements.txtpython app.pyThe PhotoStamp window should open. If it does not, see Troubleshooting below.
- Select Input Folder — click Browse… next to "Input" and choose the folder containing your photos.
- Select Output Folder (optional) — click Browse… next to "Output". If you skip this, stamped copies are saved to a
PhotoStamp Outputsubfolder inside the input folder. - Adjust settings as needed (see below).
- Preview — click Preview First Image to see how the stamp looks before committing.
- Review — use Previous / Next to move through images, and edit the per-image name or second line/date if needed.
- Stamp — click Stamp All Photos to process the entire folder. A progress bar and status message track the run. Any errors are reported at the end without stopping the batch.
PhotoStamp remembers your preferences in a local settings.json file.
- Running from source:
settings.jsonis saved in the project folder (next toapp.py). - Running the built
.exe:settings.jsonis saved in the same folder asPhotoStamp.exe.
Settings are loaded when the app starts and saved when you close the app or finish a batch stamp.
Saved items include last input/output folders, title-case preference, font, text options, date-line options, background-band options, and export options. Image data is never stored.
If settings.json is missing or corrupted, the app falls back to defaults and continues normally. The file is listed in .gitignore so it is not committed to Git.
| Setting | What it does |
|---|---|
| Title Case | Capitalises the first letter of each word in the stamp text |
| Font | Font family for the stamp text (type or pick from the list) |
| Size / Auto | Font size in pt; leave Auto checked to fit the band automatically |
| Color (Text) | Colour of the stamp text |
| Align | Horizontal text alignment: Left / Center / Right |
| Show background band | Toggle only the coloured background rectangle; stamped text still prints when this is off |
| Position | Which edge the band sits on: bottom, top, left, or right |
| Size % | Band thickness as a percentage of the image dimension it spans |
| Color (Band) | Fill colour of the band |
| Opacity | 100 % = fully opaque; 0 % = fully transparent (text-only effect) |
The stamp text comes from the filename (no extension):
- Underscores
_and dashes-are replaced with spaces. - Extra spaces are trimmed.
- Title Case is applied if the checkbox is on.
Examples:
| Filename | Stamp text (default) | Stamp text (Title Case) |
|---|---|---|
emma_johnson.jpg |
emma johnson |
Emma Johnson |
product-123-blue.png |
product 123 blue |
Product 123 Blue |
IMG_0042.jpeg |
IMG 0042 |
Img 0042 |
PhotoStamp can add a second line under the name with the date the picture was taken.
In the Date Line section:
- Check Enable date line.
- Choose a Source.
- Choose a Format.
- Click Preview First Image to confirm the layout before stamping.
The date line uses the same font family by default, with an automatically smaller size. You can set a separate date font size and date color if needed. Automatic sizing shrinks both lines so they fit inside the existing band when possible.
| Source | What it does |
|---|---|
| Auto-detect per image | Parses a filename-ending date first, then reads EXIF DateTimeOriginal, then EXIF DateTimeDigitized; falls back to file created timestamp when available, then modified timestamp |
| Use one batch date | Uses the same date for every photo |
| Manually assign per image | Uses the editable This image date field while previewing; use Previous / Next to move through images |
| Extract date from filename | Detects a date at the end of the filename and uses it as the date line |
| No date line | Disables the second line |
For auto-detect, filename dates win over EXIF and file timestamps. This is useful when the filename carries the intended date. If no filename date exists, PhotoStamp tries EXIF photo dates, then file created time, then file modified time.
File created timestamps are not always the true photo-taken date. They can change when photos are copied, downloaded, exported, or synced.
The batch date and manual per-image date fields accept common formats such as:
01/31/20262026-01-31Jan 31, 2026January 31, 2026
Manual per-image corrections are stored in memory for the current app session and are not written to settings.json.
While previewing, the Name for this image field shows the suggested displayed name. The Second line / Date field shows the suggested date line from the selected source.
- Edit the name to override the displayed name for that image.
- Clear the name to fall back to the suggested name.
- Edit the second line/date to override auto-detection, batch date, EXIF, and filename parsing.
- Clear the second line/date to intentionally suppress the date line for that image.
- Use Previous and Next to review all photos before stamping; returning to an image restores your edits.
When Extract date from filename is selected, PhotoStamp looks for conservative date patterns at the end of the name:
| Filename | Name line | Date line |
|---|---|---|
Joe Smith 013126.jpg |
Joe Smith |
January 31, 2026 |
Joe Smith 01-31-26.jpg |
Joe Smith |
January 31, 2026 |
Joe Smith 01_31_2026.jpg |
Joe Smith |
January 31, 2026 |
Joe Smith 2026-01-31.jpg |
Joe Smith |
January 31, 2026 |
Joe Smith 20260131.jpg |
Joe Smith |
January 31, 2026 |
Joe Smith Jan 31 2026.jpg |
Joe Smith |
January 31, 2026 |
Joe Smith January 31, 2026.jpg |
Joe Smith |
January 31, 2026 |
The option Remove detected date from displayed name is on by default. Two-digit years use this cutoff: 00-69 means 2000-2069, and 70-99 means 1970-1999.
Available date display formats:
January 31, 2026Jan 31, 202601/31/20261/31/262026-01-3131 Jan 2026- Custom Python
strftimeformat, such as%B %d, %Y,%m/%d/%Y, or%Y-%m-%d
If the custom format is blank or invalid, PhotoStamp falls back to January 31, 2026.
The Show background band checkbox controls only the white/colored rectangle behind the stamp.
- When it is on, PhotoStamp draws the selected band color and opacity.
- When it is off, PhotoStamp still stamps the name and optional date line directly onto the photo.
- Band position and size still define the stamp/text area either way.
If text is hard to read without a background band, turn the band back on or choose a more visible text color.
By default, PhotoStamp exports at the original image size and original file format.
Size options:
| Option | Behavior |
|---|---|
| Original size | Do not resize |
| Resize by width | Set output width and preserve aspect ratio |
| Resize by height | Set output height and preserve aspect ratio |
| Fit within box | Fit inside the given width and height while preserving aspect ratio |
| Custom width x height | Resize to exact dimensions; this may change aspect ratio |
File type options:
| Option | Behavior |
|---|---|
| Original format | Keep the source image format when practical |
| JPEG | Save as .jpg with high quality; alpha is converted to RGB |
| PNG | Save as .png |
| WEBP | Save as .webp with high quality |
PhotoStamp resizes first, then stamps onto the final output size. This keeps band thickness and auto font sizing predictable.
.jpg · .jpeg · .png · .webp · .bmp
These steps create a single PhotoStamp.exe you can share with other Windows users. They do not need Python installed to run the .exe.
PhotoStamp does not bundle any extra image or font files. It uses system fonts already on the computer (Arial on Windows when available). PyInstaller automatically includes the photostamp Python package through normal imports.
On Windows, in the project folder with the virtual environment activated:
pip install -r requirements-build.txtThis installs Pillow (runtime) and PyInstaller (build tool only).
pyinstaller --onefile --windowed --name "PhotoStamp" app.pyWhat the flags mean:
| Flag | Purpose |
|---|---|
--onefile |
Bundle everything into one PhotoStamp.exe |
--windowed |
No black console window behind the GUI |
--name "PhotoStamp" |
Name the output executable PhotoStamp.exe |
app.py |
Entry point |
After a successful build:
dist\PhotoStamp.exe
You can copy PhotoStamp.exe to any folder and double-click it to run. A settings.json file will appear next to the .exe the first time settings are saved.
Build artifacts also appear in:
build\— temporary build files (safe to delete)PhotoStamp.spec— PyInstaller spec file (created automatically; listed in.gitignore)
pyinstaller --onefile --windowed --name "PhotoStamp" app.pyOr, once PhotoStamp.spec exists:
pyinstaller PhotoStamp.specRunning from source
-
Make sure the virtual environment is activated:
.venv\Scripts\activate -
Run from the project folder:
python app.py -
If nothing appears, run without
--windowedto see error messages:python app.py
Any Python traceback will print in the terminal.
Running the built .exe
-
Try running
PhotoStamp.exefrom Command Prompt so errors are visible:dist\PhotoStamp.exe
-
Rebuild on the same Windows machine where you plan to use the app.
-
Make sure antivirus did not quarantine the file (see below).
Install dependencies inside the activated virtual environment:
.venv\Scripts\activate
pip install -r requirements.txtThen run again with python app.py.
If this happens during a PyInstaller build, install build requirements first:
pip install -r requirements-build.txtPhotoStamp prefers Arial on Windows (C:\Windows\Fonts\arial.ttf). If Arial is missing, the app falls back to another system font automatically. It should not crash.
- On Windows, Arial is normally pre-installed.
- You can pick a different font from the Font dropdown in the app.
- If text looks too small, turn off Auto font size and set a manual size.
PyInstaller builds are not code-signed by default. Windows may show “Windows protected your PC” or SmartScreen the first time you run PhotoStamp.exe.
This is common for small/local apps that are not signed with a commercial certificate. To run the app:
- Click More info
- Click Run anyway
Only run .exe files from sources you trust. For development, building the .exe yourself on your own machine is the safest option.
If you see an error about _tkinter or No module named 'tkinter', reinstall Python and enable tcl/tk and IDLE during setup. Tkinter is required for the desktop window.
PhotoStamp/
├── app.py # Entry point
├── photostamp/
│ ├── config.py # StampSettings dataclass + enums + defaults
│ ├── filename.py # Filename → stamp text
│ ├── date_utils.py # Date detection, parsing, and formatting
│ ├── exporting.py # Resize and output format helpers
│ ├── fonts.py # Cross-platform font discovery
│ ├── stamping.py # Pillow: draw band + text, save
│ ├── batch.py # Folder scan + batch loop
│ ├── settings_store.py # Load/save settings.json
│ └── gui/
│ ├── main_window.py # Main Tkinter window + all controls
│ └── preview.py # Canvas-based preview panel widget
├── requirements.txt # Runtime dependencies (Pillow)
├── requirements-build.txt # Build dependencies (adds PyInstaller)
└── README.md
TBD