Languages: English | 中文
A smooth photo display and management application, supporting multiple image formats and large-size image rendering.
- Manage photos online - Easily manage and browse photos via the web interface
- Explore map - Browse photo locations on a map
- Smart EXIF parsing - Automatically extracts metadata such as capture time, geolocation, and camera parameters
- Reverse geocoding - Automatically identifies photo shooting locations
- Multi-format support - Supports mainstream formats including JPEG, PNG, HEIC/HEIF
- Smart thumbnails - Efficient thumbnail generation using ThumbHash
- Nuxt 4 - Built on the latest Nuxt framework with SSR/SSG support
- TypeScript - Full type safety
- TailwindCSS - Modern CSS framework
- Drizzle ORM - Type-safe database ORM
- Multiple storage backends - Supports S3-compatible storage, GitHub (WIP), and local filesystem (WIP)
- CDN acceleration - Configurable CDN URL for faster photo delivery
We recommend deploying with the prebuilt Docker image. View the image on ghcr
Run with customized environment variables:
Create a .env file:
Create docker-compose.yml:
Start:
CFRAME_ADMIN_EMAIL | Email of the initial admin user | None | Yes, must be the GitHub account email used for login |
CFRAME_ADMIN_NAME | Username of the initial admin | Chronoframe | No |
CFRAME_ADMIN_PASSWORD | Password of the initial admin | CF1234@! | No |
NUXT_PUBLIC_APP_TITLE | Application title | ChronoFrame | No |
NUXT_PUBLIC_APP_SLOGAN | Application slogan | None | No |
NUXT_PUBLIC_APP_AUTHOR | Application author | None | No |
NUXT_PUBLIC_APP_AVATAR_URL | Application avatar URL | None | No |
NUXT_PUBLIC_MAPBOX_ACCESS_TOKEN | Mapbox access token for map service | None | Yes |
NUXT_MAPBOX_ACCESS_TOKEN | Mapbox access token for location info | None | No |
NUXT_STORAGE_PROVIDER | Storage provider (s3, github, local) | s3 | Yes |
NUXT_PROVIDER_S3_ENDPOINT | S3 endpoint | None | Required if provider is s3 |
NUXT_PROVIDER_S3_BUCKET | S3 bucket name | chronoframe | Required if provider is s3 |
NUXT_PROVIDER_S3_REGION | S3 bucket region | auto | Required if provider is s3 |
NUXT_PROVIDER_S3_ACCESS_KEY_ID | S3 access key ID | None | Required if provider is s3 |
NUXT_PROVIDER_S3_SECRET_ACCESS_KEY | S3 secret access key | None | Required if provider is s3 |
NUXT_PROVIDER_S3_PREFIX | S3 object prefix | photos/ | No |
NUXT_PROVIDER_S3_CDN_URL | S3 CDN URL | None | No |
NUXT_OAUTH_GITHUB_CLIENT_ID | GitHub OAuth app Client ID | None | Yes |
NUXT_OAUTH_GITHUB_CLIENT_SECRET | GitHub OAuth app Client Secret | None | Yes |
NUXT_SESSION_PASSWORD | Session encryption password (32 chars) | None | Yes |
If CFRAME_ADMIN_EMAIL and CFRAME_ADMIN_PASSWORD are not set, the default admin account is:
- Email: [email protected]
- Password: CF1234@!
- Click avatar to sign in with GitHub OAuth or use email/password login
- Go to the dashboard at /dashboard
- On the Photos page, select and upload images (supports batch & drag-and-drop)
- System will automatically parse EXIF data, generate thumbnails, and perform reverse geocoding
- Node.js 18+
- pnpm 9.0+
App will start at http://localhost:3000.
Contributions are welcome! Please:
- Fork the repo
- Create a feature branch (git checkout -b feature/amazing-feature)
- Commit changes (git commit -m 'Add some amazing feature')
- Push to branch (git push origin feature/amazing-feature)
- Open a Pull Request
- Use TypeScript for type safety
- Follow ESLint and Prettier conventions
- Update documentation accordingly
This project is licensed under the MIT License.
Timothy Yin
- Email: [email protected]
- GitHub: @HoshinoSuzumi
- Website: bh8.ga
- Gallery: lens.bh8.ga
On first startup, an admin user is created based on CFRAME_ADMIN_EMAIL, CFRAME_ADMIN_NAME, and CFRAME_ADMIN_PASSWORD. The email must match your GitHub account email used for login.
Which image formats are supported?Supported formats: JPEG, PNG, HEIC/HEIF, MOV (for Live Photos).
Why can’t I use GitHub/Local storage?Currently only S3-compatible storage is supported. GitHub and local storage support is planned.
Why is a map service required and how to configure it?The map is used to browse photo locations and render mini-maps in photo details. Currently Mapbox is used. After registering, get an access token and set it to the MAPBOX_TOKEN variable.
Why wasn’t my MOV file recognized as a Live Photo?Ensure the image (.heic) and video (.mov) share the same filename (e.g., IMG_1234.heic and IMG_1234.mov). Upload order does not matter. If not recognized, you can trigger pairing manually from the dashboard.
How do I import existing photos from storage?Direct import of existing photos is not yet supported. A directory scanning import feature is planned.
How is this different from Afilmory?Afilmory generates a manifest from photos during local/CI processing and serves them statically. ChronoFrame is a dynamic photo management app, offering online upload, management, and browsing—better for frequently updated galleries. In other words, Afilmory = static; ChronoFrame = dynamic, online upload/manage.
This project was inspired by Afilmory, another excellent personal gallery project.
Thanks to the following open-source projects and libraries: