Skip to content

Commit

Permalink
Tag search (MVP) (#65)
Browse files Browse the repository at this point in the history
  • Loading branch information
SmilyOrg authored May 18, 2023
2 parents 8af549e + 0dd98b4 commit 8bb0924
Show file tree
Hide file tree
Showing 12 changed files with 183 additions and 106 deletions.
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,13 @@ layouts.
`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.
* [ ] **Filter by tags**. You could filter by a tag, e.g. `#beach` to only
show photos with that tag. Even cooler would be to be able to search with
arbitrary boolean expressions like `#beach AND #sunset` or `#beach OR #palm`.
* [ ] **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.
Expand Down
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.19

require (
github.com/EdlinOrg/prominentcolor v1.0.0
github.com/alecthomas/assert/v2 v2.2.2
github.com/alecthomas/participle/v2 v2.0.0
github.com/deepmap/oapi-codegen v1.8.2
github.com/dgraph-io/ristretto v0.0.2
Expand Down Expand Up @@ -40,6 +41,7 @@ require (
require (
github.com/ByteArena/poly2tri-go v0.0.0-20170716161910-d102ad91854f // indirect
github.com/ajstarks/svgo v0.0.0-20200320125537-f189e35d30ca // indirect
github.com/alecthomas/repr v0.2.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/buger/jsonparser v1.0.0 // indirect
github.com/cespare/xxhash v1.1.0 // indirect
Expand All @@ -56,6 +58,7 @@ require (
github.com/gosimple/unidecode v1.0.0 // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/hashicorp/go-multierror v1.1.0 // indirect
github.com/hexops/gotextdiff v1.0.3 // indirect
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/lucasb-eyer/go-colorful v1.0.3 // indirect
Expand Down
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,11 @@ github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3
github.com/ajstarks/svgo v0.0.0-20200320125537-f189e35d30ca h1:kWzLcty5V2rzOqJM7Tp/MfSX0RMSI1x4IOLApEefYxA=
github.com/ajstarks/svgo v0.0.0-20200320125537-f189e35d30ca/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
github.com/alecthomas/assert/v2 v2.2.2 h1:Z/iVC0xZfWTaFNE6bA3z07T86hd45Xe2eLt6WVy2bbk=
github.com/alecthomas/assert/v2 v2.2.2/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ=
github.com/alecthomas/participle/v2 v2.0.0 h1:Fgrq+MbuSsJwIkw3fEj9h75vDP0Er5JzepJ0/HNHv0g=
github.com/alecthomas/participle/v2 v2.0.0/go.mod h1:rAKZdJldHu8084ojcWevWAL8KmEU+AT+Olodb+WoN2Y=
github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk=
github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
Expand Down Expand Up @@ -351,6 +353,7 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v0.0.0-20170914154624-68e816d1c783/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
Expand Down
63 changes: 56 additions & 7 deletions internal/image/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ import (
"log"
"net/http"
"path/filepath"
"strconv"
"strings"
"sync"
"time"

"photofield/internal/clip"
"photofield/internal/metrics"
"photofield/search"
"photofield/tag"

"zombiezen.com/go/sqlite"
Expand All @@ -36,6 +38,7 @@ const (
type ListOptions struct {
OrderBy ListOrder
Limit int
Query *search.Query
}

type Database struct {
Expand Down Expand Up @@ -1244,14 +1247,49 @@ func (source *Database) List(dirs []string, options ListOptions) <-chan InfoList
conn := source.pool.Get(nil)
defer source.pool.Put(conn)

sql := `
SELECT id, width, height, orientation, color, created_at_unix, created_at_tz_offset
sql := ""

tags := options.Query.QualifierValues("tag")
if len(tags) > 0 {
sql += `
WITH
`
for i := range tags {
if i > 0 {
sql += ","
}
sql += `
tag` + strconv.Itoa(i) + ` AS (
SELECT file_id, len
FROM infos_tag
WHERE tag_id IN (
SELECT id
FROM tag
WHERE name = ?
)
)
`
}
}

sql += `
SELECT infos.id, width, height, orientation, color, created_at_unix, created_at_tz_offset
FROM infos
`

if len(tags) > 0 {
for i := range tags {
sql += fmt.Sprintf(`
JOIN tag%[1]d ON id BETWEEN tag%[1]d.file_id AND tag%[1]d.file_id+tag%[1]d.len
`, i)
}
}

sql += `
WHERE path_prefix_id IN (
SELECT id
FROM prefix
WHERE
`
WHERE `

for i := range dirs {
sql += `str LIKE ? `
Expand All @@ -1267,15 +1305,21 @@ func (source *Database) List(dirs []string, options ListOptions) <-chan InfoList
switch options.OrderBy {
case None:
case DateAsc:
sql += `ORDER BY created_at_unix ASC `
sql += `
ORDER BY created_at_unix ASC
`
case DateDesc:
sql += `ORDER BY created_at_unix DESC `
sql += `
ORDER BY created_at_unix DESC
`
default:
panic("Unsupported listing order")
}

if options.Limit > 0 {
sql += `LIMIT ? `
sql += `
LIMIT ?
`
}

sql += ";"
Expand All @@ -1285,6 +1329,11 @@ func (source *Database) List(dirs []string, options ListOptions) <-chan InfoList

bindIndex := 1

for _, tag := range tags {
stmt.BindText(bindIndex, tag)
bindIndex++
}

for _, dir := range dirs {
stmt.BindText(bindIndex, dir+"%")
bindIndex++
Expand Down
17 changes: 1 addition & 16 deletions internal/layout/album.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
// . "photofield/internal"

"log"
"photofield/internal/collection"
"photofield/internal/image"
"photofield/internal/metrics"
"photofield/internal/render"
Expand Down Expand Up @@ -72,14 +71,7 @@ func LayoutAlbumEvent(layout Layout, rect render.Rect, event *AlbumEvent, scene
return rect
}

func LayoutAlbum(layout Layout, collection collection.Collection, scene *render.Scene, source *image.Source) {

limit := collection.Limit

infos := collection.GetInfos(source, image.ListOptions{
OrderBy: image.ListOrder(layout.Order),
Limit: limit,
})
func LayoutAlbum(infos <-chan image.SourcedInfo, layout Layout, scene *render.Scene, source *image.Source) {

layout.ImageSpacing = 0.02 * layout.ImageHeight
layout.LineSpacing = 0.02 * layout.ImageHeight
Expand Down Expand Up @@ -113,13 +105,6 @@ func LayoutAlbum(layout Layout, collection collection.Collection, scene *render.
scene.Photos = scene.Photos[:0]
index := 0
for info := range infos {
if limit > 0 && index >= limit {
break
}

// path, _ := source.GetImagePath(info.Id)
// println(path, info.Width, info.Height)

photoTime := info.DateTime
elapsed := photoTime.Sub(lastPhotoTime)
if elapsed > 1*time.Hour {
Expand Down
13 changes: 1 addition & 12 deletions internal/layout/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,14 @@ package layout
import (
"log"
"math"
"photofield/internal/collection"
"photofield/internal/image"
"photofield/internal/metrics"
"photofield/internal/render"

"time"
)

func LayoutSearch(layout Layout, collection collection.Collection, scene *render.Scene, source *image.Source) {

limit := collection.Limit

infos := collection.GetSimilar(source, scene.SearchEmbedding, image.ListOptions{
Limit: limit,
})
func LayoutSearch(infos <-chan image.SimilarityInfo, layout Layout, scene *render.Scene, source *image.Source) {

layout.ImageSpacing = 0.02 * layout.ImageHeight
layout.LineSpacing = 0.02 * layout.ImageHeight
Expand Down Expand Up @@ -52,10 +45,6 @@ func LayoutSearch(layout Layout, collection collection.Collection, scene *render

row := make([]SectionPhoto, 0)
for info := range infos {
if limit > 0 && index >= limit {
break
}

photo := SectionPhoto{
Photo: render.Photo{
Id: info.Id,
Expand Down
24 changes: 1 addition & 23 deletions internal/layout/strip.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,14 @@ import (
// . "photofield/internal"

"log"
"photofield/internal/collection"
"photofield/internal/image"
"photofield/internal/metrics"
"photofield/internal/render"

"time"
)

func LayoutStrip(layout Layout, collection collection.Collection, scene *render.Scene, source *image.Source) {

limit := collection.Limit

var infos <-chan image.SourcedInfo

if scene.Search != "" {
infos = image.SimilarityInfosToSourcedInfos(
collection.GetSimilar(source, scene.SearchEmbedding, image.ListOptions{
Limit: limit,
}),
)
} else {
infos = collection.GetInfos(source, image.ListOptions{
OrderBy: image.ListOrder(layout.Order),
Limit: limit,
})
}
func LayoutStrip(infos <-chan image.SourcedInfo, layout Layout, scene *render.Scene, source *image.Source) {

layout.ImageSpacing = 0.02 * layout.ViewportWidth

Expand All @@ -56,10 +38,6 @@ func LayoutStrip(layout Layout, collection collection.Collection, scene *render.
scene.Photos = scene.Photos[:0]
index := 0
for info := range infos {
if limit > 0 && index >= limit {
break
}

imageRect := render.Rect{
X: 0,
Y: 0,
Expand Down
14 changes: 1 addition & 13 deletions internal/layout/timeline.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"github.com/hako/durafmt"
"github.com/tdewolff/canvas"

"photofield/internal/collection"
"photofield/internal/image"
"photofield/internal/metrics"
"photofield/internal/render"
Expand Down Expand Up @@ -67,14 +66,7 @@ func LayoutTimelineEvent(layout Layout, rect render.Rect, event *TimelineEvent,
return rect
}

func LayoutTimeline(layout Layout, collection collection.Collection, scene *render.Scene, source *image.Source) {

limit := collection.Limit

infos := collection.GetInfos(source, image.ListOptions{
OrderBy: image.ListOrder(layout.Order),
Limit: limit,
})
func LayoutTimeline(infos <-chan image.SourcedInfo, layout Layout, scene *render.Scene, source *image.Source) {

layout.ImageSpacing = 0.02 * layout.ImageHeight
layout.LineSpacing = 0.02 * layout.ImageHeight
Expand Down Expand Up @@ -105,10 +97,6 @@ func LayoutTimeline(layout Layout, collection collection.Collection, scene *rend

index := 0
for info := range infos {
if limit > 0 && index >= limit {
break
}

photoTime := info.DateTime
elapsed := lastPhotoTime.Sub(photoTime)
if elapsed > 30*time.Minute {
Expand Down
8 changes: 1 addition & 7 deletions internal/layout/wall.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,13 @@ package layout
import (
"log"
"math"
"photofield/internal/collection"
"photofield/internal/image"
"photofield/internal/metrics"
"photofield/internal/render"
"time"
)

func LayoutWall(layout Layout, collection collection.Collection, scene *render.Scene, source *image.Source) {

infos := collection.GetInfos(source, image.ListOptions{
OrderBy: image.ListOrder(layout.Order),
Limit: collection.Limit,
})
func LayoutWall(infos <-chan image.SourcedInfo, layout Layout, scene *render.Scene, source *image.Source) {

section := Section{}

Expand Down
Loading

0 comments on commit 8bb0924

Please sign in to comment.