v1.2.0
The timelapse can now caption itself: the capture date and the subject's age, drawn from the birth date Immich already stores.
Why
A face timelapse spans years. Without a caption a viewer cannot tell whether two frames are a week apart or a decade — and the faces themselves are the one thing you cannot date by eye, because that is the whole point of the video.
Immich already knows the answer. It stores a person's birth date when you supply one, and shows their age in the web UI. This release puts it on the frames.
The footer
Opt-in. Capture date bottom left, age bottom right.
[encode.annotate]
enabled = true
age = "year_months" # days | months | year_months | off
language = "de" # en | de | fr | es | it
date_format = "DD.MM.YYYY"27.08.2023 3 Jahre, 5 Monate
Both videos are written when it is on — the plain timelapse.mp4 and timelapse-annotated.mp4. A date format you end up disliking should never cost you the clean render.
Readable by construction, not by luck. A translucent band darkens the bottom strip and the text carries an outline, so white snow and a night shot both work. Nothing above the band is touched.
Details that are deliberate
year_monthsreads the way a person speaks.3 years, 5 months, dropping a zero part — and under one month old it falls back to days, because0 monthsis wrong on exactly the frames where a baby changes fastest.- Month arithmetic uses a day-of-month correction. Someone born on the 31st is not a month old on the 28th of February; the obvious implementation says they are.
- Translations are built in, not taken from the system locale. That module depends on which locales a machine has generated, so the same config would render a different video on a Mac and on a Linux desktop. It also gets French wrong:
moisis invariable, so "plural = add an s" produces3 moiss. Plurals, month names and thousands separators (1,261/1.261/1 261) all come from a table. - Date patterns are tokens, not strftime:
YYYY MM DD, plusMMMMandMMMfor month names.D MMMM YYYYgives5 August 2026or5 août 2026depending onlanguage.
Birth dates
Read during index into a new people table and cached, so encode never touches the network. With two accounts configured, whichever one filled the field in wins — they describe the same person.
If age is enabled and Immich has no birth date, the run says so and draws the date alone rather than failing:
! encode.annotate.age is set but Immich has no birth date for this person;
annotating with the date only.
Nothing about an existing setup changes
- A
config.tomlwith no[encode.annotate]block produces exactly the video it did before — one file, no footer, nothing read. - Databases from 1.1.0 upgrade in place. The
peopletable arrives throughCREATE TABLE IF NOT EXISTS, so there is no migration to run and none to get wrong. - Pillow is imported only to draw, so the age arithmetic and the language tables stay testable in a bare environment.
586 tests, up from 514 — and 572 with 14 skipped where Pillow is absent, which is the shape CI runs.
Notes
This project was written entirely by Claude (Anthropic's AI assistant, running as Claude Code) and supervised by the repository owner. The model has never had access to the Immich instance or its API keys; all verification against real photographs was done by the owner.