Model or dataset
dai-hongtao/InkTime avatar
dai-hongtao/InkTime

InkTime: an eInk photo frame that shows you this day from years ago

InkTime 是一个可自托管的墨水屏电子相框。它用AI分析你的照片库,按值得回忆度打分,并按“历史上的今天”自动生成每日最具回忆价值的照片,让沉睡的记忆重新被看见。

658 stars102 forksPythonMIT

At a glance

What is it?
InkTime is a self-hosted eInk photo frame project. It scans your photo library, has a vision model describe and score each image, writes a one line caption, then picks the most memorable photo from this day in past years and pushes it to an ESP32 eInk display. The Python side is three scripts and a Flask server; the hardware is a 7.3 inch four colour panel.
Who is it for?
Adopt InkTime if you want a self-hosted eInk frame that shows a meaningful photo from your own library each day rather than a random one, and you are prepared to run a vision model over your library and solder an ESP32 board.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 85 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What the project actually does differently from a plain frame

InkTime is presented as a self-hosted eInk photo frame that pulls memories back out of your album. The README is careful to say what it is not. It does not show photos at random, and it is not a simple playback along a timeline. Instead it uses AI to understand what each photo is about, scores photos on how memorable they are and how good they look, writes a short piece of inspired caption text, and each day selects the most worth-seeing photo from the on-this-day pool and pushes it to an ESP32 eInk screen. The primary language is Python, the licence is MIT, the default branch is main, the last push was on 2026-07-30, and the repository publishes no GitHub releases. The design goal is a frame that behaves like a memory rather than a screensaver.

bash
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt

That is the environment setup the README recommends, with Python 3.10 or newer, and the requirements file is four pinned packages: Flask, requests, Pillow and pillow-heif, the last of which is there because phone libraries tend to store HEIF images.

The three parts: analysis, rendering, and the ESP32

The architecture splits into three pieces that run at different times on different hardware. The first is photo analysis in Python, which scans the album, calls a vision model, classifies and scores the photos, writes the captions, and stores the results in a database. The second is image rendering in Python, which selects the high scoring on-this-day photo from the database and renders it into a .bin file the ESP32 can display directly. The third is download and display on the ESP32, where the board pulls the .bin file from the server on a schedule, refreshes the eInk panel, and enters deep sleep until the next wake. The data store is photos.db, a SQLite database. This split matters for operations, because the expensive part, the model pass over the album, runs once and is then cached, while the daily part is cheap and can run from a cron job.

Running the analysis, and the three flags that matter

Before analysing, the README asks you to make sure your LM Studio instance or cloud vision model service is running and that config.py is filled in. The analysis itself is one command.

bash
python3 analyze_photos.py -j 4
python3 analyze_photos.py --debug
python3 analyze_photos.py --cache

The three flags are documented in detail. The -j or --concurrency option sets the number of concurrent processing threads and defaults to 1, and the README says to raise it when a local model or a multi channel interface has the throughput. The --debug flag prints the request body and the response body when a request fails, which is what you want when a compatible service returns something unexpected. The --cache flag reuses the previously cached album file list, and the README is explicit about its limits: it prevents rescanning the album directory on every run, it should only be turned on for the first full analysis of the album, and it should not be used in production, because newly added photos will not be found and deleted photos will not be removed from the database. The program can also be interrupted and resumed, already processed photos are not analysed again, and the README suggests you can spread the analysis of a whole album over several days. The author notes that the model used was qwen3-vl-30b, and that a smaller vision model can be used if your hardware is limited.

Rendering the day and running the server

The daily render step selects the photo and produces the binary the display consumes, and it is a single command. The server then handles both the ESP32 download endpoint and an optional review interface, also a single command.

bash
python3 render_daily_photo.py
bash
python3 server.py

The WebUI, if enabled, is a small visual frontend for looking at the descriptions and captions of the photos that have been processed, and for previewing the simulated eInk rendering. You open it in a browser on the local address 127.0.0.1 at port 8765 under the review path. The README's advice is to turn the WebUI off in config.py once things are working and keep only the ESP32 download interface. The optional exiftool installation is worth noting for the same reason: InkTime runs without it, but it may not be able to read GPS information from EXIF completely, and the README recommends the tool with a Homebrew install on macOS and the libimage-exiftool-perl package on Linux.

The download key is a path secret, not encryption

The privacy section of the README is short and worth reading closely. To avoid leaking photo privacy, the recommendation is to change DOWNLOAD_KEY so the ESP32 download path gets a random prefix used as a key, and to change the matching DAILY_PHOTO_PATH_PREFIX field in the firmware at esp32/ink-display-7C-photo/ink-display-7C-photo.ino. The README then says plainly that this is not encryption, just a simple verification path passphrase, and that a public deployment should add HTTPS or a reverse proxy with authentication, or be restricted to the local network only. That is a candid statement of the security model, and it means the server is designed to be reachable by the device and therefore by anyone who guesses the path. The config file also has a required IMAGE_DIR for the photo library path and API_CHANNELS for the vision model interface, and the project uses an OpenAI compatible API so LM Studio or another compatible service works.

The hardware side, and the licence obligations that come with it

The hardware section is unusually complete for a hobby project. The controller is an Espressif ESP32-S3-N8R8 module, and the README says you can use any ready made ESP32 development board, with the note that if you use a different board or module you should pick a model with PSRAM, at least 384K of it. The screen is a 7.3 inch four colour eInk panel, model EL073TS3 with 49 pins, driven by the GxEPD2 library and the GxEPD2_730c_GDEY073D46 constructor, and for other sizes or models you adjust the constructor against the library's hardware support list. The adapter board is a seven colour EPD board from a Bilibili maker, though most 24-pin eInk panels with SPI adapters are said to be compatible. The SPI pin assignments are given as constants, from PIN_EPD_BUSY = 14 through PIN_EPD_DIN = 9. Schematics, BOM and board files are in the esp32/pcb folder, the firmware is built with the Arduino IDE and the ESP32 Arduino Core with PSRAM enabled, and the first boot opens an access point named InkTime-xxxx with the default password 12345678, where you configure Wi-Fi, the server address and the refresh time at 192.168.4.1.

bash
chmod +x scripts/daily_render.sh
sudo -u inktime crontab -e
0 5 * * * /path/to/InkTime/scripts/daily_render.sh

The refresh cycle is one pull per day, then deep sleep, with a 60 second download timeout that also sends the board to long sleep, and the RESET button forcing an immediate fetch and refresh. Long sleep standby current is under 1mA, and the README estimates about half a year on two 18650 cells at 5000mAh. The licence obligations are the part to notice: the ESP32 firmware depends on GxEPD2 under GPL-3.0, and the README says that if you distribute the compiled firmware you must also comply with the GPL-3.0.

Editorial conclusion

Adopt InkTime if you want a self-hosted eInk frame that shows a meaningful photo from your own library each day rather than a random one, and you are prepared to run a vision model over your library and solder an ESP32 board. Do not adopt it if you want a finished consumer product, since the project is a hobby build with a custom board, and note the licence mix, because the ESP32 firmware depends on GxEPD2 under GPL-3.0 and the offline city name index is based on GeoNames under CC BY 4.0, so redistributing a compiled firmware means complying with the GPL. Before you start, change DOWNLOAD_KEY in config.py and the matching DAILY_PHOTO_PATH_PREFIX in the firmware, and put the server behind HTTPS or a reverse proxy if it is reachable from the internet.

Frequently asked questions

What is InkTime and what does it do with my photos?

It is a self-hosted eInk photo frame project. It scans your photo library, has a vision model describe and score each photo for memorability and visual appeal, writes a short caption, and each day pushes the most worthwhile photo from the on-this-day pool to an ESP32 eInk display.

How do I install and run the InkTime Python side?

Create a virtual environment with python3 -m venv venv, activate it, and run pip install -r requirements.txt, which pulls Flask, requests, Pillow and pillow-heif. Then run python3 analyze_photos.py to process the library, python3 render_daily_photo.py to render the daily image, and python3 server.py to run the download server and optional WebUI.

What does the --cache flag do in InkTime?

It reuses the previously cached album file list so the directory is not rescanned on every model run. The README says to use it only for the first full analysis of the album and never in production, because newly added photos will not be found and deleted photos will not be removed from the database.

Is the InkTime server safe to expose to the internet?

The README says the DOWNLOAD_KEY prefix is not encryption but a simple verification path passphrase, and that a public deployment should add HTTPS or a reverse proxy with authentication, or be restricted to the local network. You are advised to change DOWNLOAD_KEY in config.py and the matching DAILY_PHOTO_PATH_PREFIX in the firmware.

What licence obligations come with building an InkTime display?

The ESP32 firmware depends on GxEPD2 under GPL-3.0, and the README says that if you distribute the compiled firmware you must also comply with the GPL-3.0. The offline Chinese city name index in the project is based on GeoNames data under CC BY 4.0, and the project itself is MIT licensed.

Official sources

  1. dai-hongtao/InkTime on GitHub
  2. Issues
  3. License: MIT
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/dai-hongtao-inktime.svg)](https://hysenlabs.com/projects/dai-hongtao-inktime)

Was this useful?