Skip to content

Commit

Permalink
Various quality of life additions, tweaks, and fixes (#112)
Browse files Browse the repository at this point in the history
  • Loading branch information
SmilyOrg authored Aug 19, 2024
2 parents 4f7542b + 10e26ba commit 7c44573
Show file tree
Hide file tree
Showing 47 changed files with 2,365 additions and 684 deletions.
619 changes: 619 additions & 0 deletions CHANGELOG.md

Large diffs are not rendered by default.

130 changes: 23 additions & 107 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@
Experimental <em>fast</em> photo viewer.
<br />
<br />
<a href="https://demo.photofield.dev">Demo</a>
·
<a href="https://github.com/SmilyOrg/photofield/issues">Report Bug</a>
·
<a href="https://github.com/SmilyOrg/photofield/issues">Request Feature</a>
<a href="https://demo.photofield.dev"><img alt="live demo" src="https://img.shields.io/badge/live-demo-blue"></a>
<a href="https://photofield.dev"><img alt="docs" src="https://img.shields.io/badge/online-docs-yellow"></a>
<a href="https://github.com/SmilyOrg/photofield/blob/main/LICENSE"><img alt="License" src="https://img.shields.io/github/license/SmilyOrg/photofield"></a>
<a href="https://github.com/SmilyOrg/photofield/releases"><img alt="Version" src="https://ghcr-badge.egpl.dev/SmilyOrg/photofield/latest_tag?color=%2344cc11&ignore=latest&label=version&trim="></a>
<a href="https://github.com/SmilyOrg/photofield/pkgs/container/photofield"><img alt="Image Size" src="https://ghcr-badge.egpl.dev/SmilyOrg/photofield/size?color=%2344cc11&tag=latest&label=image+size&trim="></a>
<a href="https://github.com/SmilyOrg/photofield/actions"><img alt="GitHub Actions Workflow Status" src="https://img.shields.io/github/actions/workflow/status/SmilyOrg/photofield/.github%2Fworkflows%2Frelease.yml"></a>
<a href="https://discord.gg/qjMxfCMVqM" target="_blank"><img alt="Discord" src="https://img.shields.io/discord/1210642013997764619?logo=discord&logoColor=white"></a>
</p>
</p>

Expand Down Expand Up @@ -79,61 +81,19 @@ form of feedback to not lose track.
* **Different layouts**. Collections of photos can be displayed with different
layouts.
![layout examples](docs/assets/layouts.png)
* **Album:** chronological photos grouped by event
* **Timeline:** reverse-chronological timeline similar to Google Photos
* **Wall:** a square collage of all the photos, because zooming is fun!
* **Map:** all the photos on a map? Sure!
* [More future ideas?](https://github.com/SmilyOrg/photofield/issues/1)
* **Semantic search using [photofield-ai] (alpha)**. If you set up an AI server
and configure it in the `ai` section of the [configuration], you should be
able to search for photo contents using words like "beach sunset", "a couple
kissing", or "cat eyes".
![semantic search for "cat eyes"](docs/assets/semantic-search.jpg)
* **Tagging (alpha)**. You can tag photos with arbitrary tags. Currently tags
are only stored in the database and not in the photos themselves. You need to
enable them in the `tags` section of the [configuration] and restart the
server. This forms a foundation for many other features, see below (checked -
implemented).
* [x] **Persistent photo selection**. You can Ctrl+Click or Ctrl+Drag to
select photos. This creates a new randomly generated "selection" tag that is
persistent and shareable. It also means you can select tens of thousands of
photos without losing your progress. These tags are currently never cleaned
up and you can't do anything with it yet, so it's not useful yet, but it's
a start.
* [x] **Custom tags**. You canadd your own tags to photos, e.g. `#family` or
`#vacation`. Batch tagging not supported yet, but should be relatively easy
to add considering the selections (above) are already tags.
* [x] **EXIF tags**. Tags are automatically added from the EXIF data, e.g.
`exif:make:sony` or `exif:model:sm-g950f`. You need to enable this in the
`exif` section of the [configuration]. Only `make` and `model` are currently
supported (hardcoded).
* [x] **Filter by tags**. You can filter by a tag by searching for `tag:TAG`.
For example, you can search for `tag:fav` to only show favorited photos, or
`tag:hello tag:world` to only show photos with both `hello` and `world`
tags. This is an early version of filtering and should be more user-friendly
in the future.
* [ ] **Location tags**. Photos could be automatically tagged with the
location, e.g. `city:berlin` or `country:germany`. See #59.
* [ ] **Face recognition**. Photos could be automatically tagged with the
person's name. This would be a great way to search for photos of a specific
person.
* **Reverse geolocation**. Local, embedded reverse geolocation via [tinygpkg].
Does not need any API calls, has negligible performance impact, and supports
~50 thousand places. Currently only supported for photos with GPS coordinates
in the EXIF data and the Timeline view.

* **Flexible media/thumbnail system**. Do you have hundreds of gigabytes of existing
thumbnails from an existing system? Me too! Let's reuse those. Don't have any?
No worries, they will be generated automatically to speed up display. Here are
the currently supported thumbnail sources:
* Bespoke SQLite thumbnail database - `photofield.thumbs.db`.
* Synology Moments / Photo Station auto-generated thumbnails in `@eaDir`.
* Embedded JPEG thumbnails - `ThumbnailImage` Exif tag.
* Native Go [image](https://pkg.go.dev/image) package.
* FFmpeg on-the-fly conversion - thumbnails and full sized variants.
* Configurable via the `sources` section of the [Configuration].
* Please [open an issue] for other systems, bonus points for an idea on how to
integrate!
* **Semantic search using [photofield-ai] (alpha)**. If enabled, you can search
for photo contents using words like "beach sunset", "a couple kissing", or
"cat eyes". ![semantic search for "cat eyes"](docs/assets/semantic-search.jpg)
* **Tagging (alpha)**. You can tag and search photos with arbitrary tags. If
enabled, tags are stored in the cache database and can be used to filter
photos.
* **Reverse geolocation**. Local, embedded reverse geolocation of ~50 thousand
places via [tinygpkg] with neglibile overhead supported in the Timeline and
Flex layouts.
* **Flexible media/thumbnail system**. There are many different ways for images
and thumbnails to be generated and stored. Uses FFmpeg for on-the-fly
conversion, SQLite for caching, existing embedded JPEG thumbnails, Synology
Moments / Photo Station thumbnails, and more.
* **Single file binary**. Thanks to [Go] and [GoReleaser], all the dependencies
are packed into a [single binary file](#binaries) for most major OSes.
* **Read-only file system based collections**. Photofield never changes your
Expand Down Expand Up @@ -165,6 +125,8 @@ some time with a slow CPU and cold HDD cache.
* **No permalinks**. Deep linking to images works, but it's currently not stable
over time as IDs can change.

See the [documentation] for more information.

### Built With

* [Go] - API and server-side tile rendering
Expand Down Expand Up @@ -269,53 +231,6 @@ collections:
- /photo
```



## Usage

This section will cover some obvious uses, but also some possibly unintuitive UI
quirks that exist in the current version.

### App Bar
![App bar explanation](docs/assets/app-bar.png)

### Photo Viewer

* Click to zoom to a photo
* `Escape` or pinch out to get back to the list of photos
* Zoom in/out directly with `Ctrl/Cmd`+`Wheel`
* Pinch-to-zoom on touch devices
* Press/hold `Arrow Left` or `Arrow Right` to quickly switch between photos
* Right-click or long-tap as usual to open a custom context menu allowing you to
copy or download original photos or thumbnails.

![context menu](docs/assets/context-menu.png)

_You can open/copy/copy link the original or access any existing thumbnails
that already exist for it with the bottom list of thumbnails by pixel width._



## Maintenance

Over time the cache database can grow in size due to version upgrades and so on.
To shrink the database to its minimum size, you can _vacuum_ it. Multiple vacuums in a row have no effect as the vacuum itself rewrites the database from
the ground up.

While the vacuum is in progress, it will take twice the database size and may
take several minutes if you have lots of photos and a low-power system.

As an example it took around 5 minutes to vacuum a 260 MiB database containing around 500k photos on a DS418play. The size after vacuuming was 61 MiB as all the
leftover data from database upgrades was cleaned up.

```sh
# CLI
./photofield -vacuum
# Docker
docker exec -it photofield ./photofield -vacuum
```

## Development Setup

### Prerequisites
Expand Down Expand Up @@ -390,6 +305,7 @@ Distributed under the MIT License. See `LICENSE` for more information.
* [readme.so](https://readme.so/)

[Configuration]: #configuration
[documentation]: https://photofield.dev

[open an issue]: https://github.com/SmilyOrg/photofield/issues
[Getting Started]: #getting-started
Expand Down
16 changes: 16 additions & 0 deletions api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@ paths:
schema:
$ref: "#/components/schemas/Search"

- name: tweaks
in: query
schema:
$ref: "#/components/schemas/Tweaks"

responses:
"200":
description: List of scenes created for the specified collection
Expand Down Expand Up @@ -227,6 +232,12 @@ paths:
type: boolean
example: false

- name: quality_preset
in: query
schema:
type: string
example: "HIGH"

responses:
"200":
description: OK
Expand Down Expand Up @@ -734,6 +745,8 @@ components:
$ref: "#/components/schemas/ViewportWidth"
viewport_height:
$ref: "#/components/schemas/ViewportHeight"
tweaks:
$ref: "#/components/schemas/Tweaks"
image_height:
$ref: "#/components/schemas/ImageHeight"
layout:
Expand Down Expand Up @@ -819,6 +832,9 @@ components:
minimum: 0
example: 800

Tweaks:
type: string

ImageWidth:
type: number
minimum: 0
Expand Down
24 changes: 18 additions & 6 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,33 +125,45 @@ func loadConfig(dataDir string) (*AppConfig, error) {
}

// Expand collections
expanded := make([]collection.Collection, 0, len(appConfig.Collections))
collections := make([]collection.Collection, 0, len(appConfig.Collections))
expandedDirs := make(map[string]bool) // Track deduplicated dirs
for _, collection := range appConfig.Collections {
if collection.ExpandSubdirs {
for _, dir := range collection.Dirs {
expandedDirs[dir] = true
}
expanded = append(expanded, collection.Expand()...)
collections = append(collections, collection.Expand()...)
} else {
expanded = append(expanded, collection)
collections = append(collections, collection)
}
}
appConfig.Collections = expanded
appConfig.ExpandedPaths = make([]string, 0, len(expandedDirs))
for dir := range expandedDirs {
appConfig.ExpandedPaths = append(appConfig.ExpandedPaths, dir)
}

for i := range appConfig.Collections {
collection := &appConfig.Collections[i]
for i := range collections {
collection := &collections[i]
collection.GenerateId()
collection.Layout = strings.ToUpper(collection.Layout)
if collection.Limit > 0 && collection.IndexLimit == 0 {
collection.IndexLimit = collection.Limit
}
}

// Override earlier collection with the same ID
collectionsMap := make(map[string]int)
for i := 0; i < len(collections); i++ {
if idx, exists := collectionsMap[collections[i].Id]; exists {
collections[idx] = collections[i]
collections = append(collections[:i], collections[i+1:]...)
i--
continue
}
collectionsMap[collections[i].Id] = i
}
appConfig.Collections = collections

appConfig.Media.AI = appConfig.AI
appConfig.Media.DataDir = dataDir
appConfig.Tags.Enable = appConfig.Tags.Enable || appConfig.Tags.Enabled
Expand Down
6 changes: 5 additions & 1 deletion defaults.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ collections:
# - /second/dir
# - C:/third/windows/dir
# - ./relative/dir
#
# Later collections override earlier ones with the same name / id
# so that you can have expanded collections and override settings
# of specific collections.

# Default layout of all collections
layout:
Expand Down Expand Up @@ -108,7 +112,7 @@ media:
# Size of the image cache used while rendering images
# A larger cache might make display/rendering faster, while a smaller
# cache will conserve memory.
max_size: 256Mi
max_size: 1024Mi

# File extensions to index on the file system
extensions: [
Expand Down
10 changes: 10 additions & 0 deletions docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,16 @@ export default defineConfig({
{ text: 'Quick Start', link: '/quick-start' },
]
},
{
text: 'Features',
link: '/features',
items: [
{ text: 'Layouts', link: '/features/layouts' },
{ text: 'Search', link: '/features/search' },
{ text: 'Tags', link: '/features/tags' },
{ text: 'Reverse Geolocation', link: '/features/geolocation' },
]
},
{
text: 'Usage',
link: '/usage',
Expand Down
9 changes: 9 additions & 0 deletions docs/.vitepress/theme/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@
--vp-c-brand-1: #dd8888;
--vp-c-brand-2: #dd8888;
--vp-c-brand-3: #be6868;
--vp-code-color: #c46363;
--vp-code-bg: hsl(0, 0%, 97%);
--vp-custom-block-tip-code-bg: hsl(237, 100%, 99%);
}

:root.dark {
--vp-code-color: var(--vp-c-brand-1);
--vp-code-bg: var(--vp-c-default-soft);
--vp-custom-block-tip-code-bg: var(--vp-custom-block-tip-code-bg);
}

.VPFeature article > img {
Expand Down
Binary file added docs/assets/album.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/flex.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/highlights.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/map.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/timeline.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/wall.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions docs/features/geolocation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Reverse Geolocation

Local, embedded reverse geolocation using the custom-built [tinygpkg package].

* Supports ~50 thousand places
* Powered by https://www.geoboundaries.org/
* Adds only 16MB to build (uncompressed)
* Only supports photos with GPS coordinates in the EXIF data
* Supported in the [Timeline] and [Flex] views

[Timeline]: layouts.md#timeline
[Flex]: layouts.md#flex

[tinygpkg package]: https://github.com/SmilyOrg/tinygpkg
51 changes: 51 additions & 0 deletions docs/features/layouts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Layouts

Photofield supports various layouts to display collections of photos. Each
layout offers a unique way to organize and view your photos.

You can set the default layout for each collection in the
[configuration](../configuration), or change the currently displayed layout
through the cog icon in the top right corner.

## Album

The **Album** layout groups photos chronologically by event. This layout is
ideal for organizing photos from different events or occasions.

![Album screenshot](../assets/album.jpg)

## Timeline

The **Timeline** layout displays photos in a reverse-chronological order,
similar to Google Photos. This layout is useful for viewing recent photos first.

![Timeline layout example](../assets/timeline.jpg)

## Wall

The **Wall** layout creates a square collage of all the photos. This layout is
great for quickly browsing through a large number of photos.

![Wall layout example](../assets/wall.jpg)

## Map

The **Map** layout places all the photos on a map. This layout is perfect for
finding photos taken at specific locations.

![Map layout example](../assets/map.jpg)

## Flex

The **Flex** layout uses a variant of Knuth & Plass algorithm to create a
smarter layout, especially for photos with odd aspect ratios.

![Flex layout example](../assets/flex.png)

## Highlights

The **Highlights** layout varies the row height based on the "sameness" of the
photos. This layout is designed to make travel photo collections more skimmable
by shrinking similar and repeating photos. This layout requires AI to be enabled.

![Highlights layout example](../assets/highlights.png)
Loading

0 comments on commit 7c44573

Please sign in to comment.