251 lines
8.6 KiB
Markdown
251 lines
8.6 KiB
Markdown
# CRT Photobooth
|
||
|
||
A fullscreen photobooth for a Linux laptop mirrored to a 4:3 CRT. Guests press
|
||
the button (any mouse button) to take a 3-photo shoot with countdowns and
|
||
flashes. After 4 shoots, all 12 photos are printed on one contact sheet.
|
||
When idle for 5 minutes, a fish-tank screensaver protects the CRT from burn-in.
|
||
|
||
## Setup on a new laptop
|
||
|
||
1. **Install Python 3.11 or newer** (check with `python3 --version`).
|
||
|
||
2. **Install the dependencies:**
|
||
|
||
```bash
|
||
pip install pygame-ce opencv-python pillow
|
||
```
|
||
|
||
Note: it's `pygame-ce`, not `pygame` — the community edition has wheels
|
||
for newer Python versions. If pip refuses because the system Python is
|
||
"externally managed", either use `pip install --user ...` or create a
|
||
virtual environment:
|
||
|
||
```bash
|
||
python3 -m venv ~/photobooth-venv
|
||
~/photobooth-venv/bin/pip install pygame-ce opencv-python pillow
|
||
# then run with: ~/photobooth-venv/bin/python photobooth.py
|
||
```
|
||
|
||
3. **Plug in the Macally webcam** and confirm Linux sees it:
|
||
|
||
```bash
|
||
ls /dev/v4l/by-id/
|
||
```
|
||
|
||
You should see something like `usb-SolidYear_Macally_USB2.0Camera-video-index0`.
|
||
The default config finds it automatically by name.
|
||
|
||
4. **Set up the printer.** The booth prints via CUPS with the `lp` command.
|
||
Check what's configured and set a default:
|
||
|
||
```bash
|
||
lpstat -p # list printers
|
||
lpoptions -d NAME # set the default printer
|
||
lpstat -p -d # verify: should show a default destination
|
||
```
|
||
|
||
Print a test page from the printer settings GUI to make sure it works
|
||
before an event.
|
||
|
||
5. **Copy this whole folder** to the laptop and run it:
|
||
|
||
```bash
|
||
python3 photobooth.py
|
||
```
|
||
|
||
Add `--windowed` to test in a window instead of fullscreen.
|
||
**Press Esc or Q to quit.**
|
||
|
||
## Running headless (lid closed)
|
||
|
||
While it is running, the booth holds systemd keep-awake locks
|
||
(`systemd-inhibit`, block mode: `sleep`, `idle`, and `handle-lid-switch`
|
||
when no desktop environment already holds it). The laptop will not suspend
|
||
or idle-sleep, and — on machines where logind manages the lid — closing
|
||
the lid does nothing, so the laptop can run tucked away behind the CRT.
|
||
The locks are released as soon as the program exits.
|
||
|
||
If the machine still suspends when the lid closes (some desktop
|
||
environments keep the lid lock for themselves), disable lid suspend on
|
||
that machine: put `HandleLidSwitch=ignore` in
|
||
`/etc/systemd/logind.conf.d/photobooth.conf` (needs root, applies even
|
||
when the booth is not running) or use the desktop's own power settings.
|
||
|
||
## Running on a Raspberry Pi (no desktop)
|
||
|
||
The booth needs no desktop environment. On a console-only machine (no X
|
||
or Wayland session) it automatically tells SDL to draw straight to the
|
||
screen via the KMS/DRM driver, so it runs fine on Raspberry Pi OS Lite.
|
||
|
||
1. **Install Raspberry Pi OS Lite (64-bit)** and boot with the screen,
|
||
camera, and mouse plugged in.
|
||
|
||
2. **Install the system packages:**
|
||
|
||
```bash
|
||
sudo apt install python3-venv cups fonts-dejavu-core
|
||
```
|
||
|
||
(`fonts-dejavu-core` is for the contact-sheet footer text — Lite
|
||
doesn't ship it and the fallback font is tiny.)
|
||
|
||
3. **Create a virtual environment and install the dependencies:**
|
||
|
||
```bash
|
||
python3 -m venv ~/photobooth-venv
|
||
~/photobooth-venv/bin/pip install pygame-ce opencv-python-headless pillow
|
||
```
|
||
|
||
Note the **headless** OpenCV build: the regular `opencv-python` wheel
|
||
needs X libraries that Lite doesn't have, and the booth uses no GUI
|
||
features. If the pygame-ce wheel's SDL lacks the KMS/DRM driver
|
||
(`pygame.error: kmsdrm not available`), use the distro build instead
|
||
(`sudo apt install python3-pygame`) — the booth uses nothing
|
||
pygame-ce-specific.
|
||
|
||
4. **Give your user access to the screen and input devices:**
|
||
|
||
```bash
|
||
sudo usermod -aG video,render,input $USER # then log out and back in
|
||
```
|
||
|
||
5. **Check which screen modes are available.** The Pi can only use modes
|
||
the display (or HDMI adapter) advertises:
|
||
|
||
```bash
|
||
cat /sys/class/drm/card*-HDMI-A-*/modes
|
||
```
|
||
|
||
If 800x600 isn't listed, set `[display]` width/height in `config.toml`
|
||
to one of the listed modes. For a 4:3 CRT behind an HDMI-to-coax
|
||
adapter, 720x480 fills the screen correctly (same trick as DVDs).
|
||
Everything scales to `[display]` automatically — no code changes
|
||
needed.
|
||
|
||
6. **Set up the printer** with CUPS from the command line:
|
||
|
||
```bash
|
||
lpinfo -v # find the printer's URI (USB printers: usb://...)
|
||
lpinfo -m # list available drivers
|
||
sudo lpadmin -p PRINTER -E -v <uri> -m <driver>
|
||
lpoptions -d PRINTER
|
||
lpstat -p -d # verify
|
||
```
|
||
|
||
Then set `printer = "PRINTER"` in `config.toml` to match.
|
||
|
||
7. **Run it** from the console:
|
||
|
||
```bash
|
||
~/photobooth-venv/bin/python photobooth.py
|
||
```
|
||
|
||
### Starting automatically at boot
|
||
|
||
Create `/etc/systemd/system/photobooth.service` (adjust the user and
|
||
paths to match your setup):
|
||
|
||
```ini
|
||
[Unit]
|
||
Description=CRT Photobooth
|
||
|
||
[Service]
|
||
User=pi
|
||
SupplementaryGroups=video render input
|
||
WorkingDirectory=/home/pi/nissaphotobooth
|
||
ExecStart=/home/pi/photobooth-venv/bin/python photobooth.py
|
||
Restart=on-failure
|
||
|
||
[Install]
|
||
WantedBy=multi-user.target
|
||
```
|
||
|
||
Then:
|
||
|
||
```bash
|
||
sudo systemctl enable --now photobooth
|
||
```
|
||
|
||
Quit with Esc/Q on a keyboard, or `sudo systemctl stop photobooth` over
|
||
SSH. The keep-awake locks work the same as on a laptop (a Pi rarely
|
||
sleeps anyway). The `auto-macally` camera lookup already waits ~15
|
||
seconds for USB devices to enumerate, so no boot-order tweaks are needed.
|
||
|
||
## Customizing
|
||
|
||
- **All settings** live in `config.toml` — camera choice, screen text,
|
||
countdown lengths, screensaver timeout, printer options. Comments in the
|
||
file explain each one.
|
||
- **Idle screen background:** replace `assets/background.png` (any size,
|
||
it gets scaled to the screen — 4:3 looks best, e.g. 800×600).
|
||
- **Fish:** drop your fish drawings into the `fish/` folder as PNGs with
|
||
transparent backgrounds. Draw them **facing right** — the program flips
|
||
them automatically when they swim left. Bubbles come out of the front
|
||
(mouth) end. Delete `placeholder_fish.png` once you have real fish.
|
||
|
||
## Switching cameras
|
||
|
||
In `config.toml`, `device = "auto-macally"` finds the Macally cam by name.
|
||
To use a different camera:
|
||
|
||
- `device = "auto-integrated"` — match another camera by (partial) name
|
||
from `ls /dev/v4l/by-id/`, case-insensitive
|
||
- `device = "/dev/video2"` — an exact device path
|
||
- `device = "0"` — a plain index
|
||
|
||
## Where things go
|
||
|
||
- `photos/` — individual shots for the current print cycle. Deleted
|
||
automatically after a successful print. (If the program is restarted
|
||
mid-cycle, photos here are counted so no progress is lost.)
|
||
- `contact_sheets/` — every printed sheet, kept as a backup in case of
|
||
printer trouble. Each sheet has two files: a `.jpg` (handy for viewing)
|
||
and a `.pdf` twin — the PDF is a full US-letter page at exactly 300 DPI
|
||
and is what actually gets sent to the printer. Delete them manually now
|
||
and then, or set `delete_contact_sheets = true` in the config to remove
|
||
each one right after it prints.
|
||
|
||
## If printing fails
|
||
|
||
The booth doesn't lose anything: the contact sheet is saved in
|
||
`contact_sheets/`, an error shows on the idle screen, and you can print the
|
||
sheet by hand with:
|
||
|
||
```bash
|
||
lp -o media=letter -o fit-to-page contact_sheets/sheet_XXXX.pdf
|
||
```
|
||
|
||
(Print the `.pdf`, not the `.jpg` — the PDF's page size is exact, so the
|
||
printer can't stretch the photos.)
|
||
|
||
Common causes: printer off or unplugged (`lpstat -p` says "disabled" —
|
||
re-enable with `cupsenable PRINTER_NAME`), or no default printer set.
|
||
|
||
## Troubleshooting the camera
|
||
|
||
Test the camera on its own (uses the device from `config.toml`):
|
||
|
||
```bash
|
||
python3 camera.py
|
||
```
|
||
|
||
It first prints a pre-flight dump of every camera the system sees
|
||
(`/dev/v4l/by-id/` names, device nodes, and what each node identifies as),
|
||
then reports which device was matched, resolved, and opened, plus the OpenCV
|
||
version. If the booth ever uses the wrong camera, run this and read the
|
||
output — it shows exactly where the selection went sideways.
|
||
|
||
**If it reports black frames** (or the live preview shows a warning): the
|
||
Macally cam's chipset sometimes wedges and streams pure black until it is
|
||
power-cycled. **Unplug the camera, plug it back in, and restart the
|
||
program.** Worth doing a quick test shoot at the start of an event.
|
||
|
||
## Heads-up
|
||
|
||
- The Brother HL-5470DW is a **monochrome laser** — contact sheets print
|
||
in black & white.
|
||
- The live preview is mirrored (like a mirror) so posing feels natural, but
|
||
saved photos are not mirrored. Set `mirror_preview = false` to change that.
|
||
- CRT overscan may crop the very edges of the screen; once the CRT is
|
||
connected, adjust text positions/sizes in code or the CRT's own controls.
|