{"id":20548585,"url":"https://github.com/smilyorg/photofield","last_synced_at":"2025-05-16T04:05:56.903Z","repository":{"id":37847780,"uuid":"272271486","full_name":"SmilyOrg/photofield","owner":"SmilyOrg","description":"Experimental fast photo viewer.","archived":false,"fork":false,"pushed_at":"2025-03-27T22:52:33.000Z","size":13845,"stargazers_count":488,"open_issues_count":30,"forks_count":9,"subscribers_count":10,"default_branch":"main","last_synced_at":"2025-05-16T04:05:50.675Z","etag":null,"topics":["gallery","golang","photos","photoviewer","self-hosted","vue3"],"latest_commit_sha":null,"homepage":"","language":"Go","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/SmilyOrg.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"docs/contributing.md","funding":null,"license":"LICENSE","code_of_conduct":null,"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}},"created_at":"2020-06-14T19:42:00.000Z","updated_at":"2025-05-09T21:33:46.000Z","dependencies_parsed_at":"2023-02-08T06:31:13.685Z","dependency_job_id":"c34c6eb0-5746-4b3d-9802-80f5a9b142d5","html_url":"https://github.com/SmilyOrg/photofield","commit_stats":{"total_commits":220,"total_committers":4,"mean_commits":55.0,"dds":"0.036363636363636376","last_synced_commit":"926b69d8c06ad96fa786007e21d4c911e8ddd615"},"previous_names":[],"tags_count":54,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SmilyOrg%2Fphotofield","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SmilyOrg%2Fphotofield/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SmilyOrg%2Fphotofield/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SmilyOrg%2Fphotofield/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SmilyOrg","download_url":"https://codeload.github.com/SmilyOrg/photofield/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254464895,"owners_count":22075570,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["gallery","golang","photos","photoviewer","self-hosted","vue3"],"created_at":"2024-11-16T02:13:57.349Z","updated_at":"2025-05-16T04:05:51.895Z","avatar_url":"https://github.com/SmilyOrg.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003c!-- HEADER --\u003e\n\u003cbr /\u003e\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/smilyorg/photofield\"\u003e\n    \u003cimg src=\"ui/public/android-chrome-192x192.png\" alt=\"Logo\" width=\"80\" height=\"80\"\u003e\n  \u003c/a\u003e\n\n  \u003ch3 align=\"center\"\u003ePhotofield\u003c/h3\u003e\n\n  \u003cp align=\"center\"\u003e\n    Experimental \u003cem\u003efast\u003c/em\u003e photo viewer.\n    \u003cbr /\u003e\n    \u003cbr /\u003e\n    \u003ca href=\"https://demo.photofield.dev\"\u003e\u003cimg alt=\"live demo\" src=\"https://img.shields.io/badge/live-demo-blue\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://photofield.dev\"\u003e\u003cimg alt=\"docs\" src=\"https://img.shields.io/badge/online-docs-yellow\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://github.com/SmilyOrg/photofield/blob/main/LICENSE\"\u003e\u003cimg alt=\"License\" src=\"https://img.shields.io/github/license/SmilyOrg/photofield\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://github.com/SmilyOrg/photofield/releases\"\u003e\u003cimg alt=\"Version\" src=\"https://ghcr-badge.egpl.dev/SmilyOrg/photofield/latest_tag?color=%2344cc11\u0026ignore=latest\u0026label=version\u0026trim=\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://github.com/SmilyOrg/photofield/pkgs/container/photofield\"\u003e\u003cimg alt=\"Image Size\" src=\"https://ghcr-badge.egpl.dev/SmilyOrg/photofield/size?color=%2344cc11\u0026tag=latest\u0026label=image+size\u0026trim=\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://github.com/SmilyOrg/photofield/actions\"\u003e\u003cimg alt=\"GitHub Actions Workflow Status\" src=\"https://img.shields.io/github/actions/workflow/status/SmilyOrg/photofield/.github%2Fworkflows%2Frelease.yml\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://discord.gg/qjMxfCMVqM\" target=\"_blank\"\u003e\u003cimg alt=\"Discord\" src=\"https://img.shields.io/discord/1210642013997764619?logo=discord\u0026logoColor=white\"\u003e\u003c/a\u003e\n  \u003c/p\u003e\n\u003c/p\u003e\n\n\n\n\u003c!-- TABLE OF CONTENTS --\u003e\n\u003cdetails open=\"open\"\u003e\n  \u003csummary\u003eTable of Contents\u003c/summary\u003e\n  \u003col\u003e\n    \u003cli\u003e\n      \u003ca href=\"#about\"\u003eAbout\u003c/a\u003e\n      \u003cul\u003e\n        \u003cli\u003e\u003ca href=\"#features\"\u003eFeatures\u003c/a\u003e\u003c/li\u003e\n        \u003cli\u003e\u003ca href=\"#limitations\"\u003eLimitations\u003c/a\u003e\u003c/li\u003e\n        \u003cli\u003e\u003ca href=\"#built-with\"\u003eBuilt With\u003c/a\u003e\u003c/li\u003e\n      \u003c/ul\u003e\n    \u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"#getting-started\"\u003eGetting Started\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"#configuration\"\u003eConfiguration\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"#usage\"\u003eUsage\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"#maintenance\"\u003eMaintenance\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"#development-setup\"\u003eDevelopment Setup\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"#contributing\"\u003eContributing\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"#license\"\u003eLicense\u003c/a\u003e\u003c/li\u003e\n    \u003cli\u003e\u003ca href=\"#acknowledgements\"\u003eAcknowledgements\u003c/a\u003e\u003c/li\u003e\n  \u003c/ol\u003e\n\u003c/details\u003e\n\n\n\n## About\n\n![Zoom to logo within a 43k images](docs/assets/logo-zoom.gif)\n\n_Zoom to logo within a sample of 43k images from [open-images-dataset], i7-5820K 6-Core CPU, NVMe SSD_\n\nPhotofield is a photo viewer built to mainly push the limits of what is possible\nin terms of the number of photos visible at the same time and at the speed at\nwhich they are displayed. The goal is to be as fast or faster than Google Photos\non commodity hardware while displaying more photos at the same time. It is\nnon-invasive and can be used either completely standalone or complementing other\nphoto gallery software.\n\n\n### Features\n\n* **Seamless zoomable interface**. Thanks to tiled image loading supported by\n[OpenLayers] and the API implementing tile rendering, you can switch between\nlevels of detail seamlessly without loading a special detailed or\nfullscreen view.\n\n  ![Seamless zoom to giraffe face](docs/assets/seamless-zoom.gif)\n\n* **Progressive multi-resolution loading**. Not only are thumbnails used to show\na single photo quicker, the whole layout is progressively loaded, so even if you\nmove through photos quickly or zoom around, you will almost always have some\nform of feedback to not lose track.\n\n  ![Progressive load of a deer](docs/assets/progressive-load.gif)\n\n* **Different layouts**. Collections of photos can be displayed with different\nlayouts.\n![layout examples](docs/assets/layouts.png)\n* **Semantic search using [photofield-ai]**. If enabled, you can search\n  for photo contents using words like \"beach sunset\", \"a couple kissing\", or\n  \"cat eyes\". ![semantic search for \"cat eyes\"](docs/assets/semantic-search.jpg)\n* **Tagging (alpha)**. You can tag and search photos with arbitrary tags. If\n  enabled, tags are stored in the cache database and can be used to filter\n  photos.\n* **Reverse geolocation**. Local, embedded reverse geolocation of ~50 thousand\n  places via [tinygpkg] with neglibile overhead supported in the Timeline and\n  Flex layouts.\n* **Flexible media/thumbnail system**. There are many different ways for images\n  and thumbnails to be generated and stored. Uses FFmpeg for on-the-fly\n  conversion, SQLite for caching, existing embedded JPEG thumbnails, Synology\n  Moments / Photo Station thumbnails, and more.\n* **Single file binary**. Thanks to [Go] and [GoReleaser], all the dependencies\nare packed into a [single binary file](#binaries) for most major OSes.\n* **Read-only file system based collections**. Photofield never changes your\nphotos, thumbnails or directories. You are encouraged to even mount your photos\nas read-only to ensure this. The file system is the source of truth, everything\nelse is just a more or less stale cache.\n* **Fast indexing**. Thanks to [godirwalk], file indexing practically runs at\nthe speed of the file system 1000-10000 files/sec on fast SSD and hot cache.\nEXIF metadata and [prominent color] are extracted as separate follow-up\noperations and run at up to ~200 files/sec and ~1000 files/sec on a fast system.\n* **Basic video support**. Videos are supported, however the user experience\nis not great yet as there are some usability quirks. Different resolutions are\nsupported if they have been previously transcoded, but there is no on-the-fly\ntranscoding supported right now.\n\n### Limitations\n\n* **No photo details (yet)**. There is no way to show metadata of a photo in the\nUI at this point.\n* **Not optimized for many clients**. As a lot of the normally client-side\nstate is kept on the server, you will likely run into CPU or Memory problems\nwith more than a few simultaneous users.\n* **No user accounts**. Not the focus right now. You can define separate\ncollections for separate users based on the directory structure, but there is no\nauthentication or authorization support.\n* **Initial load can be slow**. All the photos need to be laid out when you\nfirst load a page in a specific window size and configuration, which can take\nsome time with a slow CPU and cold HDD cache.\n* **No permalinks**. Deep linking to images works, but it's currently not stable\nover time as IDs can change. \n\nSee the [documentation] for more information.\n\n### Built With\n\n* [Go] - API and server-side tile rendering\n* [Canvas (tdewolff)](https://github.com/tdewolff/canvas) - vector rendering in\n  Go\n* [SQLite 3 (zombiezen)](https://github.com/zombiezen/go-sqlite) -\n  fast single-file database/cache\n* [Vue 3] - frontend framework\n* [BalmUI] - Material UI components\n* [OpenLayers] - in-browser tiled image rendering\n* [OpenSeadragon] (honorary mention) - tiled image rendering library used previously\n* [+ more Go libraries](go.mod)\n* [+ more npm libraries](ui/package.json)\n\n\n\n\n## Getting Started\n\n### Docker\n\nMake sure you create an empty `data` directory in the working directory and that\nyou put some photos in a `photos` directory.\n\n```sh\ndocker run -p 8080:8080 -v \"$PWD/data:/app/data\" -v \"$PWD/photos:/app/photos:ro\" ghcr.io/smilyorg/photofield\n```\n\nThe cache database will be persisted to the `data` dir and the app should be\naccessible at http://localhost:8080. It should show the `photos` collection by\ndefault. For further configuration, create a `configuration.yaml` in the\n`data` dir.\n\n\u003cdetails\u003e\n  \u003csummary\u003e\u003ccode\u003edocker-compose.yaml\u003c/code\u003e example\u003c/summary\u003e\n  \n  This example binds the usual Synology Moments photo directories and assumes\n  a certain path structure, modify to your needs graciously. It also assumes you\n  have configured the `/photo` and `/user` directories as collections in\n  the `configuration.yaml`.\n  ```yaml\n  version: '3.3'\n  services:\n\n    photofield:\n      image: ghcr.io/smilyorg/photofield:latest\n      ports:\n        - 8080:8080\n      volumes:\n        - /volume1/docker/photofield/data:/app/data\n        - /volume1/photo/:/photo:ro\n        - /volume1/homes/ExampleUser/Drive/Moments:/exampleuser:ro\n  ```\n\u003c/details\u003e\n\n### Binaries\n\n1. [Download and unpack a release].\n2. Run `./photofield` or double-click on `photofield.exe` to start the server.\n3. Open http://localhost:8080, folders in the working directory will be\ndisplayed as collections. 🎉\n\n* 📝 Create a `configuration.yaml` in the working dir to configure the app\n* 🕵️‍♀️ Install [exiftool] and add it to PATH for better metadata support\n(esp. for video)\n* ⚪ Set the `PHOTOFIELD_DATA_DIR` environment variable to change the path where\nthe app looks for the `configuration.yaml` and cache database\n\n[Download and unpack a release]: https://github.com/SmilyOrg/photofield/releases\n[exiftool]: https://exiftool.org/\n\n\n\n## Configuration\n\nYou can configure the app via `configuration.yaml`.\n\nThe location of the file depends on the installation method, see\n[Getting Started].\n\nThe following is a minimal `configuration.yaml` example, see [`defaults.yaml`]\nfor all options.\n\n```yaml\ncollections:\n  # Normal Album-type collection\n  - name: Vacation Photos\n    dirs:\n      - /photo/vacation-photos\n\n  # Timeline collection (similar to Google Photos)\n  - name: My Timeline\n    layout: timeline\n    dirs:\n      - /photo/myphotos\n      - /exampleuser\n\n  # Create collections from sub-directories based on their name\n  - expand_subdirs: true\n    expand_sort: desc\n    dirs:\n      - /photo\n```\n\n## Development Setup\n\n### Prerequisites\n\n* [Go] - for the backend / API server\n* [Node.js] - for the frontend\n* [just] - to run common commands conveniently\n* [watchexec] - for auto-reloading the Go server\n* sh-like shell (e.g. sh, bash, busybox) - required by `just`\n* [exiftool] - for testing metadata extraction\n\n**[Scoop] (Windows)**: `scoop install busybox just exiftool watchexec`\n\n### Installation\n\n1. Clone the repo\n   ```sh\n   git clone https://github.com/smilyorg/photofield.git\n   ```\n2. Install Go dependencies\n   ```sh\n   go get\n   ```\n3. Install NPM packages\n   ```sh\n   cd ui\n   npm install\n   ```\n\n### Running\n\nRun both the API server and the UI server in separate terminals. They are set\nup to work with each other by default with the API server running at port `8080`\nand the UI server on port `3000`.\n\n`just` is [just] as defined in the [prerequisites](#prerequisites).\n\n#### API\n\n* `just watch` the source files and auto-reload the server using [watchexec]\n* or `just run` the server\n\n#### UI\n\n* `just ui` to start a hot-reloading development server\n* or run from within the `ui` folder\n  ```sh\n  cd ui\n  npm run dev\n  ```\n\n\n\n## Contributing\n\nPull requests are welcome. For major changes, please open an issue first to\ndiscuss what you would like to change.\n\n\n\n## License\n\nDistributed under the MIT License. See `LICENSE` for more information.\n\n\n\n## Acknowledgements\n* [Open Images Dataset][open-images-dataset]\n* [geoBoundaries](https://www.geoboundaries.org/) for geographic boundary data used for reverse geolocation\n* [sams96/rgeo](https://github.com/sams96/rgeo) for previous reverse geolocation implementation and inspiration\n* [Best-README-Template](https://github.com/othneildrew/Best-README-Template)\n* [readme.so](https://readme.so/)\n\n[Configuration]: #configuration\n[documentation]: https://photofield.dev\n\n[open an issue]: https://github.com/SmilyOrg/photofield/issues\n[Getting Started]: #getting-started\n[`defaults.yaml`]: defaults.yaml\n\n[open-images-dataset]: https://opensource.google/projects/open-images-dataset\n\n[Scoop]: https://scoop.sh/\n[just]: https://github.com/casey/just\n[watchexec]: https://github.com/watchexec/watchexec\n\n[Go]: https://golang.org/\n[GoReleaser]: https://github.com/goreleaser/goreleaser\n[godirwalk]: https://github.com/karrick/godirwalk\n[prominent color]: https://github.com/EdlinOrg/prominentcolor\n\n[OpenLayers]: https://openlayers.org/\n[OpenSeadragon]: https://openseadragon.github.io/\n[Node.js]: https://nodejs.org/\n[Vue 3]: https://v3.vuejs.org/\n[BalmUI]: https://next-material.balmjs.com/\n[photofield-ai]: https://github.com/smilyorg/photofield-ai\n[tinygpkg]: https://github.com/smilyorg/tinygpkg\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsmilyorg%2Fphotofield","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsmilyorg%2Fphotofield","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsmilyorg%2Fphotofield/lists"}