{"id":43647387,"url":"https://github.com/hoshinosuzumi/chronoframe","last_synced_at":"2026-04-08T05:01:21.054Z","repository":{"id":312758029,"uuid":"1042231422","full_name":"HoshinoSuzumi/chronoframe","owner":"HoshinoSuzumi","description":"Self-hosted personal gallery application with online photo management and albums, supporting Live/Motion Photos, EXIF parsing, geolocation recognition, and an explore map.","archived":false,"fork":false,"pushed_at":"2026-04-07T10:09:53.000Z","size":18114,"stargazers_count":1721,"open_issues_count":45,"forks_count":118,"subscribers_count":4,"default_branch":"main","last_synced_at":"2026-04-07T11:29:17.785Z","etag":null,"topics":["albums","exif-extraction","geocoding","live-photo","motion-photo","photo-gallery","photo-sharing","webgl-viewer"],"latest_commit_sha":null,"homepage":"https://chronoframe.bh8.ga/","language":"Vue","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/HoshinoSuzumi.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":["HoshinoSuzumi"],"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"lfx_crowdfunding":null,"polar":null,"buy_me_a_coffee":null,"thanks_dev":null,"custom":null}},"created_at":"2025-08-21T17:27:31.000Z","updated_at":"2026-04-07T10:09:50.000Z","dependencies_parsed_at":"2025-09-20T19:16:19.091Z","dependency_job_id":"e0bf8f68-e4eb-4745-96ae-339f87aac608","html_url":"https://github.com/HoshinoSuzumi/chronoframe","commit_stats":null,"previous_names":["hoshinosuzumi/chronoframe"],"tags_count":112,"template":false,"template_full_name":null,"purl":"pkg:github/HoshinoSuzumi/chronoframe","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HoshinoSuzumi%2Fchronoframe","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HoshinoSuzumi%2Fchronoframe/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HoshinoSuzumi%2Fchronoframe/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HoshinoSuzumi%2Fchronoframe/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/HoshinoSuzumi","download_url":"https://codeload.github.com/HoshinoSuzumi/chronoframe/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/HoshinoSuzumi%2Fchronoframe/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31540826,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-07T16:28:08.000Z","status":"online","status_checked_at":"2026-04-08T02:00:06.127Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["albums","exif-extraction","geocoding","live-photo","motion-photo","photo-gallery","photo-sharing","webgl-viewer"],"created_at":"2026-02-04T19:02:31.157Z","updated_at":"2026-04-08T05:01:21.048Z","avatar_url":"https://github.com/HoshinoSuzumi.png","language":"Vue","funding_links":["https://github.com/sponsors/HoshinoSuzumi"],"categories":[],"sub_categories":[],"readme":"# ChronoFrame\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://socialify.git.ci/HoshinoSuzumi/chronoframe/image?custom_description=Self-hosted+personal+gallery+application.\u0026description=1\u0026font=KoHo\u0026forks=0\u0026issues=0\u0026logo=https%3A%2F%2Fgithub.com%2FHoshinoSuzumi%2Fchronoframe%2Fraw%2Frefs%2Fheads%2Fmain%2Fpublic%2Ffavicon.svg\u0026name=1\u0026owner=1\u0026pattern=Plus\u0026pulls=0\u0026stargazers=0\u0026theme=Auto\" alt=\"Chronoframe\"\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/HoshinoSuzumi/chronoframe/releases/latest\"\u003e\n    \u003cimg src=\"https://badgen.net/github/release/HoshinoSuzumi/chronoframe/stable?icon=docker\u0026label=stable\" alt=\"Latest Release\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/HoshinoSuzumi/chronoframe/releases?q=beta\u0026expanded=false\"\u003e\n    \u003cimg src=\"https://badgen.net/github/release/HoshinoSuzumi/chronoframe?icon=docker\u0026label=nightly\" alt=\"Latest Nightly Release\"\u003e\n  \u003c/a\u003e\n  \u003cimg src=\"https://img.shields.io/badge/License-MIT-green.svg\" alt=\"License\"\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://discord.gg/https://discord.gg/U4vQuydP2m\"\u003e\n    \u003cimg src=\"https://dcbadge.limes.pink/api/server/https://discord.gg/U4vQuydP2m\" alt=\"Discord Server\" /\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://hellogithub.com/repository/HoshinoSuzumi/chronoframe\" target=\"_blank\"\u003e\u003cimg src=\"https://api.hellogithub.com/v1/widgets/recommend.svg?rid=947d47ffe8404985908b266e187dec99\u0026claim_uid=kLVoiAFPJaBtr1D\u0026theme=neutral\" alt=\"Featured｜HelloGitHub\" style=\"width: 250px; height: 54px;\" width=\"250\" height=\"54\" /\u003e\u003c/a\u003e\n  \u003ca href=\"https://www.producthunt.com/products/chronoframe?embed=true\u0026utm_source=badge-featured\u0026utm_medium=badge\u0026utm_source=badge-chronoframe\" target=\"_blank\"\u003e\u003cimg src=\"https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=1029556\u0026theme=neutral\u0026t=1761159404569\" alt=\"ChronoFrame - Self\u0026#0045;hosted\u0026#0032;photo\u0026#0032;gallery\u0026#0032;for\u0026#0032;photographers\u0026#0046; | Product Hunt\" style=\"width: 250px; height: 54px;\" width=\"250\" height=\"54\" /\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n**Languages:** English | [中文](README_zh.md)\n\nA smooth photo display and management application, supporting multiple image formats and large-size image rendering.\n\n[Live Demo: TimoYin's Mems](https://lens.bh8.ga)\n\n## ✨ Features\n\n### 🖼️ Powerful Photo Management\n\n- **Manage photos online** - Easily manage and browse photos via the web interface\n- **Explore map** - Browse photo locations on a map\n- **Smart EXIF parsing** - Automatically extracts metadata such as capture time, geolocation, and camera parameters\n- **Reverse geocoding** - Automatically identifies photo shooting locations\n- **Multi-format support** - Supports mainstream formats including JPEG, PNG, HEIC/HEIF\n- **Smart thumbnails** - Efficient thumbnail generation using ThumbHash\n\n### 🔧 Modern Tech Stack\n\n- **Nuxt 4** - Built on the latest Nuxt framework with SSR/SSG support\n- **TypeScript** - Full type safety\n- **TailwindCSS** - Modern CSS framework\n- **Drizzle ORM** - Type-safe database ORM\n\n### ☁️ Flexible Storage Solutions\n\n- **Multiple storage backends** - Supports S3-compatible storage, local filesystem\n- **CDN acceleration** - Configurable CDN URL for faster photo delivery\n\n## 🐳 Deployment\n\nWe recommend deploying with the prebuilt Docker image. [View the image on ghcr](https://github.com/HoshinoSuzumi/chronoframe/pkgs/container/chronoframe)\n\nCreate a `.env` file and configure environment variables.\n\nBelow is a **minimal configuration** example. For complete configuration options, see [Configuration Guide](https://chronoframe.bh8.ga/guide/configuration.html):\n\n```bash\n# Admin email (required)\nCFRAME_ADMIN_EMAIL=\n# Admin username (optional, default Chronoframe)\nCFRAME_ADMIN_NAME=\n# Admin password (optional, default CF1234@!)\nCFRAME_ADMIN_PASSWORD=\n\n# Site metadata (all optional)\nNUXT_PUBLIC_APP_TITLE=\nNUXT_PUBLIC_APP_SLOGAN=\nNUXT_PUBLIC_APP_AUTHOR=\nNUXT_PUBLIC_APP_AVATAR_URL=\n\n# Map provider (maplibre/mapbox)\nNUXT_PUBLIC_MAP_PROVIDER=maplibre\n# MapTiler access token for MapLibre\nNUXT_PUBLIC_MAP_MAPLIBRE_TOKEN=\n# Mapbox access token for Mapbox\nNUXT_PUBLIC_MAPBOX_ACCESS_TOKEN=\n\n# Mapbox unrestricted token (optional, reverse geocoding)\nNUXT_MAPBOX_ACCESS_TOKEN=\n\n# Storage provider (local, s3 or openlist)\nNUXT_STORAGE_PROVIDER=local\nNUXT_PROVIDER_LOCAL_PATH=/app/data/storage\n\n# Session password (32‑char random string, required)\nNUXT_SESSION_PASSWORD=\n```\n\n### Pull Image\n\nUse the published image on GitHub Container Registry and Docker Hub. Choose the source that works best for your network:\n\n#### [GitHub Container Registry (GHCR)](https://github.com/HoshinoSuzumi/chronoframe/pkgs/container/chronoframe)\n\n```bash\ndocker pull ghcr.io/hoshinosuzumi/chronoframe:latest\n```\n\n#### [Docker Hub](https://hub.docker.com/r/hoshinosuzumi/chronoframe)\n\n```bash\ndocker pull hoshinosuzumi/chronoframe:latest\n```\n\n### Docker\n\nRun with customized environment variables:\n\n```bash\ndocker run -d --name chronoframe -p 3000:3000 -v $(pwd)/data:/app/data --env-file .env ghcr.io/hoshinosuzumi/chronoframe:latest\n```\n\n### Docker Compose\n\nCreate docker-compose.yml:\n\n```yaml\nservices:\n  chronoframe:\n    image: ghcr.io/hoshinosuzumi/chronoframe:latest\n    container_name: chronoframe\n    restart: unless-stopped\n    ports:\n      - '3000:3000'\n    volumes:\n      - ./data:/app/data\n    env_file:\n      - .env\n```\n\nStart:\n\n```bash\ndocker compose up -d\n```\n\n## 📖 User Guide\n\n\u003e If `CFRAME_ADMIN_EMAIL` and `CFRAME_ADMIN_PASSWORD` are not set, the default admin account is:\n\u003e\n\u003e - Email: `admin@chronoframe.com`\n\u003e - Password: `CF1234@!`\n\n### Logging into the Dashboard\n\n1. Click avatar to sign in with GitHub OAuth or use email/password login\n\n### Uploading Photos\n\n1. Go to the dashboard at /dashboard\n2. On the Photos page, select and upload images (supports batch \u0026 drag-and-drop)\n3. System will automatically parse EXIF data, generate thumbnails, and perform reverse geocoding\n\n## 📸 Screenshots\n\n![Gallery](./docs/images/screenshot1.png)\n![Photo Detail](./docs/images/screenshot2.png)\n![Map Explore](./docs/images/screenshot3.png)\n![Dashboard](./docs/images/screenshot4.png)\n\n## 🛠️ Development\n\n### Requirements\n\n- Node.js 18+\n- pnpm 9.0+\n\n### Install dependencies\n\n```bash\n# With pnpm (recommended)\npnpm install\n\n# Or with other package managers\nnpm install\nyarn install\n```\n\n### Configure environment variables\n\n```bash\ncp .env.example .env\n```\n\n### Initialize database\n\n```bash\n# 2. Generate migration files (optional)\npnpm db:generate\n\n# 3. Run database migrations\npnpm db:migrate\n```\n\n### Start development server\n\n```bash\npnpm dev\n```\n\nApp will start at http://localhost:3000.\n\n### Project Structure\n\n```\nchronoframe/\n├── app/                    # Nuxt app\n│   ├── components/         # Components\n│   ├── pages/              # Page routes\n│   ├── composables/        # Composables\n│   └── stores/             # Pinia stores\n├── packages/\n│   └── webgl-image/        # WebGL image viewer\n├── server/\n│   ├── api/                # API routes\n│   ├── database/           # DB schema \u0026 migrations\n│   └── services/           # Business logic services\n└── shared/                 # Shared types \u0026 utils\n```\n\n### Build commands\n\n```bash\n# Development (with dependencies build)\npnpm dev\n\n# Build only dependencies\npnpm build:deps\n\n# Production build\npnpm build\n\n# Database operations\npnpm db:generate    # Generate migration files\npnpm db:migrate     # Run migrations\n\n# Preview production build\npnpm preview\n```\n\n## 🤝 Contributing\n\nContributions are welcome! Please:\n\n1. Fork the repo\n2. Create a feature branch (git checkout -b feature/amazing-feature)\n3. Commit changes (git commit -m 'Add some amazing feature')\n4. Push to branch (git push origin feature/amazing-feature)\n5. Open a Pull Request\n\n### Coding Guidelines\n\n- Use TypeScript for type safety\n- Follow ESLint and Prettier conventions\n- Update documentation accordingly\n\n## 📄 License\n\nThis project is licensed under the MIT License.\n\n## 👤 Author\n\n**Timothy Yin**\n\n- Email: master@uniiem.com\n- GitHub: @HoshinoSuzumi\n- Website: bh8.ga\n- Gallery: lens.bh8.ga\n\n## ❓ FAQ\n\n\u003cdetails\u003e\n  \u003csummary\u003eHow is the admin user created?\u003c/summary\u003e\n  \u003cp\u003e\n    On first startup, an admin user is created based on \u003ccode\u003eCFRAME_ADMIN_EMAIL\u003c/code\u003e, \u003ccode\u003eCFRAME_ADMIN_NAME\u003c/code\u003e, and \u003ccode\u003eCFRAME_ADMIN_PASSWORD\u003c/code\u003e. The email must match your GitHub account email used for login.\n  \u003c/p\u003e\n\u003c/details\u003e\n\u003cdetails\u003e\n  \u003csummary\u003eWhich image formats are supported?\u003c/summary\u003e\n  \u003cp\u003e\n    Supported formats: JPEG, PNG, HEIC/HEIF, MOV (for Live Photos).\n  \u003c/p\u003e\n\u003c/details\u003e\n\u003cdetails\u003e\n  \u003csummary\u003eWhy can’t I use GitHub/Local storage?\u003c/summary\u003e\n  \u003cp\u003e\n    Currently only S3-compatible storage is supported. GitHub and local storage support is planned.\n  \u003c/p\u003e\n\u003c/details\u003e\n\u003cdetails\u003e\n  \u003csummary\u003eWhy is a map service required and how to configure it?\u003c/summary\u003e\n  \u003cp\u003e\n    The map is used to browse photo locations and render mini-maps in photo details. Currently Mapbox is used. After registering, \u003ca href=\"https://console.mapbox.com/account/access-tokens/\"\u003eget an access token\u003c/a\u003e and set it to the \u003ccode\u003eMAPBOX_TOKEN\u003c/code\u003e variable.\n  \u003c/p\u003e\n\u003c/details\u003e\n\u003cdetails\u003e\n  \u003csummary\u003eWhy wasn’t my MOV file recognized as a Live Photo?\u003c/summary\u003e\n  \u003cp\u003e\n    Ensure the image (.heic) and video (.mov) share the same filename (e.g., \u003ccode\u003eIMG_1234.heic\u003c/code\u003e and \u003ccode\u003eIMG_1234.mov\u003c/code\u003e). Upload order does not matter. If not recognized, you can trigger pairing manually from the dashboard.\n  \u003c/p\u003e\n\u003c/details\u003e\n\u003cdetails\u003e\n  \u003csummary\u003eHow do I import existing photos from storage?\u003c/summary\u003e\n  \u003cp\u003e\n    Direct import of existing photos is not yet supported. A directory scanning import feature is planned.\n  \u003c/p\u003e\n\u003c/details\u003e\n\u003cdetails\u003e\n  \u003csummary\u003eHow is this different from Afilmory?\u003c/summary\u003e\n  \u003cp\u003e\n    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.\n    In other words, Afilmory = static; ChronoFrame = dynamic, online upload/manage.\n  \u003c/p\u003e\n\u003c/details\u003e\n\n## 🙏 Acknowledgements\n\nThis project was inspired by [Afilmory](https://github.com/Afilmory/afilmory), another excellent personal gallery project.\n\nThanks to the following open-source projects and libraries:\n\n- [Nuxt](https://nuxt.com/)\n- [TailwindCSS](https://tailwindcss.com/)\n- [Drizzle ORM](https://orm.drizzle.team/)\n\n## ⭐️ Star History\n\n\u003ca href=\"https://www.star-history.com/#HoshinoSuzumi/chronoframe\u0026type=date\u0026legend=top-left\"\u003e\n \u003cpicture\u003e\n   \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"https://api.star-history.com/svg?repos=HoshinoSuzumi/chronoframe\u0026type=date\u0026theme=dark\u0026legend=top-left\" /\u003e\n   \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"https://api.star-history.com/svg?repos=HoshinoSuzumi/chronoframe\u0026type=date\u0026legend=top-left\" /\u003e\n   \u003cimg alt=\"Star History Chart\" src=\"https://api.star-history.com/svg?repos=HoshinoSuzumi/chronoframe\u0026type=date\u0026legend=top-left\" /\u003e\n \u003c/picture\u003e\n\u003c/a\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhoshinosuzumi%2Fchronoframe","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhoshinosuzumi%2Fchronoframe","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhoshinosuzumi%2Fchronoframe/lists"}