Note
Hi :)
I've been taking a photo of myself every day since 2024, but I found it very tedious to manually clip all the photos into a timelapse video, so I wrote some code to automate that back then. Since I have not really found anything better, I decided to share it here. Hope it helps!
If you have any questions or feedback, feel free to open an issue or reach out.
Turn a folder of daily portrait photos into a smooth timelapse video where your face stays perfectly centered and level. Faces are detected and aligned with Google's MediaPipe Face Landmarker, so the eyes sit at a fixed position in every frame regardless of head tilt, distance, or framing.
Note: This repository contains only the code. It ships with no photos — you supply your own (see Usage below).
- Rename by date — reads each photo's EXIF capture timestamp and renames it
to
YYYY-MM-DD_HH-MM-SS.jpgso frames are processed in chronological order. - Align faces —
FaceAlignerfinds the eye landmarks and rotates, scales, and crops each image so the eyes land on a fixed coordinate in a 1080×1920 frame. - Build the video — compiles the aligned frames into a 20 fps MP4 with OpenCV. Already-processed frames are cached, so re-runs are fast.
- Add music (optional) — pass
--musicto mix in an audio track; see Adding music below.
Programm/ ← this repository
├── main.py orchestrator: rename → align → render → (optional) mix music
├── face_aligner.py FaceAligner (MediaPipe eye-landmark alignment)
├── audio_mixer.py optional music muxing (moviepy), used with --music
├── face_landmarker.task pre-trained MediaPipe model (bundled)
├── requirements.txt
├── run.sh convenience wrapper (venv + cleanup + run)
└── CLAUDE.md notes for the Claude Code agent
<parent folder>/ ← your data lives here, one level up (git-ignored)
├── original_Photos/ put your raw photos here
├── centered_Photos/ aligned frames (generated)
└── final_video/ output MP4 (generated)
main.py derives all paths from its own location — the data folders are expected
in the parent of Programm/. Nothing is hardcoded, so you can place the
Programm/ folder wherever you like.
Requires Python 3.12 (MediaPipe/OpenCV wheels are not yet available for 3.13+).
git clone <your-repo-url>
cd <repo> # the cloned "Programm" folder
python3.12 -m venv venv
./venv/bin/python -m pip install -r requirements.txt-
Create an
original_Photos/folder in the parent directory of this repo and drop your daily photos in it (.jpg,.jpeg, or.png). -
Run the pipeline:
./run.sh # or: source venv/bin/activate && python main.py -
Find the result at
../final_video/timelapse_centered_v1.mp4.
Photos without a detectable face are skipped with a warning. Re-running only processes new photos.
Music is opt-in — pass --music with a path to an audio file and it gets
mixed into the final video. Omit it and the output is silent, exactly as
before.
./run.sh --music ../music/track.mp3
./run.sh --music ../music/track.mp3 --music-mode trim
./run.sh --music ../music/track.mp3 --music-mode match-length
./run.sh --music ../music/track.mp3 --fade 3--music-mode loop(default) — loops the track to fill the whole video, fading out over the last--fadeseconds (default 2s).--music-mode trim— plays the track once and cuts it (with a fade-out) at the end of the video; if the track is shorter than the video, the rest plays silent.--music-mode match-length— instead of adjusting the audio, the video's frame rate is chosen so its duration matches the track exactly.
Music is muxed in with moviepy, which re-encodes the output as H.264/AAC —
this also makes the file more broadly compatible than the raw mp4v output
from the silent path. The first time you use --music, moviepy may
download a small static ffmpeg binary; no manual setup needed.
Declared in requirements.txt:
- mediapipe — facial-landmark detection
- opencv-python (
cv2) — image transforms and video encoding - numpy — alignment math
- ExifRead — reads capture dates from photo EXIF
- Pillow, moviepy — pulled in as transitive/optional deps