Backend: Prepare database for advanced filtering and grouping #154
Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
parent
de6503646c
commit
ca8cfffc24
26 changed files with 927 additions and 518 deletions
|
@ -60,6 +60,9 @@ class Config {
|
|||
case "countries":
|
||||
this.values.count.countries += data.count;
|
||||
break;
|
||||
case "places":
|
||||
this.values.count.places += data.count;
|
||||
break;
|
||||
case "labels":
|
||||
this.values.count.labels += data.count;
|
||||
break;
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
{text: '', value: '', align: 'center', sortable: false, class: 'p-col-select'},
|
||||
{text: this.$gettext('Title'), value: 'PhotoTitle'},
|
||||
{text: this.$gettext('Taken At'), value: 'TakenAt'},
|
||||
{text: this.$gettext('Location'), value: 'LocPlace'},
|
||||
{text: this.$gettext('Location'), value: 'LocLabel'},
|
||||
{text: this.$gettext('Camera'), value: 'CameraModel'},
|
||||
{text: this.$gettext('Favorite'), value: 'PhotoFavorite', align: 'left'},
|
||||
],
|
||||
|
|
|
@ -104,8 +104,8 @@ class Photo extends Abstract {
|
|||
}
|
||||
|
||||
getLocation() {
|
||||
if (this.LocPlace) {
|
||||
return this.LocPlace;
|
||||
if (this.LocLabel) {
|
||||
return this.LocLabel;
|
||||
}
|
||||
|
||||
return "Unknown";
|
||||
|
|
|
@ -135,14 +135,14 @@ describe("model/photo", () => {
|
|||
});
|
||||
|
||||
it("should get location", () => {
|
||||
const values = {ID: 5, PhotoTitle: "Crazy Cat", LocationID: 6, LocType: "viewpoint", LocPlace: "Cape Point, South Africa", LocCountry: "South Africa"};
|
||||
const values = {ID: 5, PhotoTitle: "Crazy Cat", LocationID: 6, LocType: "viewpoint", LocLabel: "Cape Point, South Africa", LocCountry: "South Africa"};
|
||||
const photo = new Photo(values);
|
||||
const result = photo.getLocation();
|
||||
assert.equal(result, "Cape Point, South Africa");
|
||||
});
|
||||
|
||||
it("should get location", () => {
|
||||
const values = {ID: 5, PhotoTitle: "Crazy Cat", LocationID: 6, LocType: "viewpoint", LocPlace: "Cape Point, State, South Africa", LocCountry: "South Africa", LocCity: "Cape Town", LocCounty: "County", LocState: "State"};
|
||||
const values = {ID: 5, PhotoTitle: "Crazy Cat", LocationID: 6, LocType: "viewpoint", LocLabel: "Cape Point, State, South Africa", LocCountry: "South Africa", LocCity: "Cape Town", LocCounty: "County", LocState: "State"};
|
||||
const photo = new Photo(values);
|
||||
const result = photo.getLocation();
|
||||
assert.equal(result, "Cape Point, State, South Africa");
|
||||
|
|
462
internal/colors/colors.go
Normal file
462
internal/colors/colors.go
Normal file
|
@ -0,0 +1,462 @@
|
|||
/*
|
||||
This package encapsulates session storage.
|
||||
|
||||
Additional information can be found in our Developer Guide:
|
||||
|
||||
https://github.com/photoprism/photoprism/wiki
|
||||
*/
|
||||
package colors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"strings"
|
||||
|
||||
"github.com/lucasb-eyer/go-colorful"
|
||||
)
|
||||
|
||||
type ColorPerception struct {
|
||||
Colors Colors
|
||||
MainColor Color
|
||||
Luminance LightMap
|
||||
Chroma Chroma
|
||||
}
|
||||
|
||||
type Color uint16
|
||||
type Colors []Color
|
||||
|
||||
type Chroma uint8
|
||||
type Luminance uint8
|
||||
type LightMap []Luminance
|
||||
|
||||
const (
|
||||
Black Color = iota
|
||||
Brown
|
||||
Grey
|
||||
White
|
||||
Purple
|
||||
Gold
|
||||
Blue
|
||||
Cyan
|
||||
Teal
|
||||
Green
|
||||
Lime
|
||||
Yellow
|
||||
Magenta
|
||||
Orange
|
||||
Red
|
||||
Pink
|
||||
)
|
||||
|
||||
var All = Colors{
|
||||
Red,
|
||||
Magenta,
|
||||
Pink,
|
||||
Orange,
|
||||
Gold,
|
||||
Yellow,
|
||||
Lime,
|
||||
Green,
|
||||
Teal,
|
||||
Cyan,
|
||||
Blue,
|
||||
Purple,
|
||||
Brown,
|
||||
White,
|
||||
Grey,
|
||||
Black,
|
||||
}
|
||||
|
||||
var Names = map[Color]string{
|
||||
Black: "dark", // 0
|
||||
Brown: "brown", // 1
|
||||
Grey: "grey", // 2
|
||||
White: "bright", // 3
|
||||
Purple: "purple", // 4
|
||||
Gold: "gold", // 5
|
||||
Blue: "blue", // 6
|
||||
Cyan: "cyan", // 7
|
||||
Teal: "teal", // 8
|
||||
Green: "green", // 9
|
||||
Lime: "lime", // A
|
||||
Yellow: "yellow", // B
|
||||
Magenta: "magenta", // C
|
||||
Orange: "orange", // D
|
||||
Red: "red", // E
|
||||
Pink: "pink", // F
|
||||
}
|
||||
|
||||
var Weights = map[Color]uint16{
|
||||
Black: 2,
|
||||
Brown: 2,
|
||||
Grey: 1,
|
||||
White: 2,
|
||||
Purple: 4,
|
||||
Gold: 4,
|
||||
Blue: 3,
|
||||
Cyan: 4,
|
||||
Teal: 4,
|
||||
Green: 3,
|
||||
Lime: 5,
|
||||
Yellow: 5,
|
||||
Magenta: 5,
|
||||
Orange: 4,
|
||||
Red: 4,
|
||||
Pink: 4,
|
||||
}
|
||||
|
||||
func (c Color) Name() string {
|
||||
return Names[c]
|
||||
}
|
||||
|
||||
func (c Color) Hex() string {
|
||||
return fmt.Sprintf("%X", c)
|
||||
}
|
||||
|
||||
func (c Colors) Hex() (result string) {
|
||||
for _, indexedColor := range c {
|
||||
result += indexedColor.Hex()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (c Colors) List() []map[string]string {
|
||||
result := make([]map[string]string, 0, len(c))
|
||||
|
||||
for _, c := range c {
|
||||
result = append(result, map[string]string{"name": c.Name(), "label": strings.Title(c.Name()), "example": ColorExamples[c]})
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (c Chroma) Hex() string {
|
||||
return fmt.Sprintf("%X", c)
|
||||
}
|
||||
|
||||
func (c Chroma) Uint() uint {
|
||||
return uint(c)
|
||||
}
|
||||
|
||||
func (c Chroma) Int() int {
|
||||
return int(c)
|
||||
}
|
||||
|
||||
func (l Luminance) Hex() string {
|
||||
return fmt.Sprintf("%X", l)
|
||||
}
|
||||
|
||||
func (m LightMap) Hex() (result string) {
|
||||
for _, luminance := range m {
|
||||
result += luminance.Hex()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
var ColorExamples = map[Color]string{
|
||||
Red: "#E57373",
|
||||
Magenta: "#FF00FF",
|
||||
Pink: "#F06292",
|
||||
Orange: "#FFB74D",
|
||||
Brown: "#A1887F",
|
||||
Gold: "#FFD54F",
|
||||
Yellow: "#FFF176",
|
||||
Lime: "#DCE775",
|
||||
Green: "#81C784",
|
||||
Teal: "#4DB6AC",
|
||||
Cyan: "#4DD0E1",
|
||||
Blue: "#64B5F6",
|
||||
Purple: "#BA68C8",
|
||||
White: "#F5F5F5",
|
||||
Grey: "#BDBDBD",
|
||||
Black: "#333333",
|
||||
}
|
||||
|
||||
var ColorMap = map[color.RGBA]Color{
|
||||
{0x00, 0x00, 0x00, 0xff}: Black,
|
||||
{0xa1, 0x88, 0x7f, 0xff}: Brown,
|
||||
{0x8d, 0x6e, 0x63, 0xff}: Brown,
|
||||
{0xa0, 0x7f, 0x6c, 0xff}: Brown,
|
||||
{0x9b, 0x7b, 0x5b, 0xff}: Brown,
|
||||
{0x75, 0x64, 0x5b, 0xff}: Brown,
|
||||
{0x79, 0x55, 0x48, 0xff}: Brown,
|
||||
{0x6d, 0x4c, 0x41, 0xff}: Brown,
|
||||
{0x5d, 0x40, 0x37, 0xff}: Brown,
|
||||
{0x9b, 0x61, 0x36, 0xff}: Brown,
|
||||
{0xc1, 0xa4, 0x87, 0xff}: Brown,
|
||||
{0xaa, 0x80, 0x62, 0xff}: Brown,
|
||||
{0x6b, 0x55, 0x46, 0xff}: Brown,
|
||||
{0xb4, 0xb5, 0x9c, 0xff}: Brown,
|
||||
{0xb2, 0xb4, 0x9b, 0xff}: Green,
|
||||
{0xe0, 0xe0, 0xe0, 0xff}: Grey,
|
||||
{0x9E, 0x9E, 0x9E, 0xff}: Grey,
|
||||
{0x75, 0x75, 0x75, 0xff}: Grey,
|
||||
{0x61, 0x61, 0x61, 0xff}: Grey,
|
||||
{0x42, 0x42, 0x42, 0xff}: Grey,
|
||||
{0x84, 0x7a, 0x72, 0xff}: Grey,
|
||||
{0xdf, 0xe0, 0xe1, 0xff}: Grey,
|
||||
{0xFF, 0xFF, 0xFF, 0xff}: White,
|
||||
{0xe4, 0xe4, 0xe4, 0xff}: White,
|
||||
{0xe7, 0xe7, 0xe7, 0xff}: White,
|
||||
{0xf3, 0xe5, 0xf5, 0xff}: Purple,
|
||||
{0xe1, 0xbe, 0xe7, 0xff}: Purple,
|
||||
{0xce, 0x93, 0xd8, 0xff}: Purple,
|
||||
{0xba, 0x68, 0xc8, 0xff}: Purple,
|
||||
{0xab, 0x47, 0xbc, 0xff}: Purple,
|
||||
{0x9c, 0x27, 0xb0, 0xff}: Purple,
|
||||
{0x9b, 0x31, 0x8f, 0xff}: Purple,
|
||||
{0x86, 0x00, 0x7e, 0xff}: Purple,
|
||||
{0x8e, 0x24, 0xaa, 0xff}: Purple,
|
||||
{0x7b, 0x1f, 0xa2, 0xff}: Purple,
|
||||
{0x6a, 0x1b, 0x9a, 0xff}: Purple,
|
||||
{0x4a, 0x14, 0x8c, 0xff}: Purple,
|
||||
{0xaa, 0x00, 0xff, 0xff}: Purple,
|
||||
{0xed, 0xe7, 0xf6, 0xff}: Purple,
|
||||
{0xd1, 0xc4, 0xe9, 0xff}: Purple,
|
||||
{0xb3, 0x9d, 0xdb, 0xff}: Purple,
|
||||
{0x95, 0x75, 0xcd, 0xff}: Purple,
|
||||
{0x7e, 0x57, 0xc2, 0xff}: Purple,
|
||||
{0x5e, 0x35, 0xb1, 0xff}: Purple,
|
||||
{0x67, 0x3a, 0xb7, 0xff}: Purple,
|
||||
{0x51, 0x2d, 0xa8, 0xff}: Purple,
|
||||
{0x45, 0x27, 0xa0, 0xff}: Purple,
|
||||
{0x31, 0x1b, 0x92, 0xff}: Purple,
|
||||
{0xb3, 0x88, 0xff, 0xff}: Purple,
|
||||
{0x7c, 0x4d, 0xff, 0xff}: Purple,
|
||||
{0x8e, 0x64, 0x93, 0xff}: Purple,
|
||||
{0x5e, 0x3a, 0x5e, 0xff}: Purple,
|
||||
{0x44, 0x0e, 0x79, 0xff}: Purple,
|
||||
{0x48, 0x36, 0x78, 0xff}: Purple,
|
||||
{0x4e, 0x38, 0x80, 0xff}: Purple,
|
||||
{0x3b, 0x0e, 0x79, 0xff}: Purple,
|
||||
{0x3F, 0x51, 0xB5, 0xff}: Blue,
|
||||
{0xc5, 0xca, 0xe9, 0xff}: Blue,
|
||||
{0x5c, 0x6b, 0xc0, 0xff}: Blue,
|
||||
{0x39, 0x49, 0xab, 0xff}: Blue,
|
||||
{0x30, 0x3f, 0x9f, 0xff}: Blue,
|
||||
{0x28, 0x35, 0x93, 0xff}: Blue,
|
||||
{0x1a, 0x23, 0x7e, 0xff}: Blue,
|
||||
{0x53, 0x6d, 0xfe, 0xff}: Blue,
|
||||
{0x3d, 0x5a, 0xfe, 0xff}: Blue,
|
||||
{0x30, 0x4f, 0xfe, 0xff}: Blue,
|
||||
{0x21, 0x96, 0xF3, 0xff}: Blue,
|
||||
{0xbb, 0xde, 0xfb, 0xff}: Blue,
|
||||
{0x90, 0xca, 0xf9, 0xff}: Blue,
|
||||
{0x64, 0xb5, 0xf6, 0xff}: Blue,
|
||||
{0x42, 0xa5, 0xf5, 0xff}: Blue,
|
||||
{0x1e, 0x88, 0xe5, 0xff}: Blue,
|
||||
{0x19, 0x76, 0xd2, 0xff}: Blue,
|
||||
{0x15, 0x65, 0xc0, 0xff}: Blue,
|
||||
{0x0d, 0x47, 0xa1, 0xff}: Blue,
|
||||
{0x82, 0xb1, 0xff, 0xff}: Blue,
|
||||
{0x44, 0x8a, 0xff, 0xff}: Blue,
|
||||
{0x29, 0x79, 0xff, 0xff}: Blue,
|
||||
{0x29, 0x62, 0xff, 0xff}: Blue,
|
||||
{0x03, 0xa9, 0xf6, 0xff}: Blue,
|
||||
{0xb3, 0xe5, 0xfc, 0xff}: Blue,
|
||||
{0x81, 0xd4, 0xfa, 0xff}: Blue,
|
||||
{0x4f, 0xc3, 0xf7, 0xff}: Blue,
|
||||
{0x29, 0xb6, 0xf6, 0xff}: Blue,
|
||||
{0x03, 0x9b, 0xe5, 0xff}: Blue,
|
||||
{0x02, 0x88, 0xd1, 0xff}: Blue,
|
||||
{0x02, 0x77, 0xbd, 0xff}: Blue,
|
||||
{0x01, 0x57, 0x9b, 0xff}: Blue,
|
||||
{0x80, 0xd8, 0xff, 0xff}: Blue,
|
||||
{0x40, 0xc4, 0xff, 0xff}: Blue,
|
||||
{0x00, 0xb0, 0xff, 0xff}: Blue,
|
||||
{0x00, 0x91, 0xea, 0xff}: Blue,
|
||||
{0x60, 0x7d, 0x8b, 0xff}: Blue,
|
||||
{0x78, 0x90, 0x9c, 0xff}: Blue,
|
||||
{0x54, 0x6e, 0x7a, 0xff}: Blue,
|
||||
{0x37, 0x47, 0x4f, 0xff}: Blue,
|
||||
{0xe4, 0xeb, 0xfd, 0xff}: Blue,
|
||||
{0x7d, 0xd3, 0xea, 0xff}: Blue,
|
||||
{0x07, 0x63, 0x99, 0xff}: Blue,
|
||||
{0x28, 0x44, 0x6b, 0xff}: Blue,
|
||||
{0x4a, 0xc8, 0xf5, 0xff}: Blue,
|
||||
{0x08, 0x00, 0xf4, 0xff}: Blue,
|
||||
{0x01, 0x2d, 0x5f, 0xff}: Blue,
|
||||
{0xb2, 0xeb, 0xf2, 0xff}: Cyan,
|
||||
{0x80, 0xde, 0xea, 0xff}: Cyan,
|
||||
{0x4d, 0xd0, 0xe1, 0xff}: Cyan,
|
||||
{0x26, 0xc6, 0xda, 0xff}: Cyan,
|
||||
{0x00, 0xb8, 0xd4, 0xff}: Cyan,
|
||||
{0x00, 0xBC, 0xD4, 0xff}: Cyan,
|
||||
{0x00, 0xac, 0xc1, 0xff}: Cyan,
|
||||
{0x00, 0x97, 0xa7, 0xff}: Cyan,
|
||||
{0x00, 0x83, 0x8f, 0xff}: Cyan,
|
||||
{0x00, 0x60, 0x64, 0xff}: Cyan,
|
||||
{0x84, 0xff, 0xff, 0xff}: Cyan,
|
||||
{0x18, 0xff, 0xff, 0xff}: Cyan,
|
||||
{0x00, 0xe5, 0xff, 0xff}: Cyan,
|
||||
{0x00, 0x96, 0x88, 0xff}: Teal,
|
||||
{0x00, 0x89, 0x7b, 0xff}: Teal,
|
||||
{0x00, 0x79, 0x6b, 0xff}: Teal,
|
||||
{0x00, 0x69, 0x5c, 0xff}: Teal,
|
||||
{0x04, 0x5d, 0x5c, 0xff}: Teal,
|
||||
{0x24, 0x5a, 0x5f, 0xff}: Teal,
|
||||
{0x03, 0x45, 0x4f, 0xff}: Teal,
|
||||
{0x2c, 0x54, 0x5e, 0xff}: Teal,
|
||||
{0x17, 0x47, 0x41, 0xff}: Teal,
|
||||
{0xe8, 0xf5, 0xe9, 0xff}: Green,
|
||||
{0xc8, 0xe6, 0xc9, 0xff}: Green,
|
||||
{0xab, 0xc7, 0xb0, 0xff}: Green,
|
||||
{0xa5, 0xd6, 0xa7, 0xff}: Green,
|
||||
{0x81, 0xc7, 0x84, 0xff}: Green,
|
||||
{0x66, 0xbb, 0x6a, 0xff}: Green,
|
||||
{0x4C, 0xAF, 0x50, 0xff}: Green,
|
||||
{0x43, 0xa0, 0x47, 0xff}: Green,
|
||||
{0x38, 0x8e, 0x3c, 0xff}: Green,
|
||||
{0x2e, 0x7d, 0x32, 0xff}: Green,
|
||||
{0x1b, 0x5e, 0x20, 0xff}: Green,
|
||||
{0xf1, 0xf8, 0xe9, 0xff}: Green,
|
||||
{0xdc, 0xed, 0xc8, 0xff}: Green,
|
||||
{0xc5, 0xe1, 0xa5, 0xff}: Green,
|
||||
{0xae, 0xd5, 0x81, 0xff}: Green,
|
||||
{0x8b, 0xc3, 0x4a, 0xff}: Green,
|
||||
{0x9c, 0xcc, 0x65, 0xff}: Green,
|
||||
{0x7c, 0xb3, 0x42, 0xff}: Green,
|
||||
{0x68, 0x9f, 0x38, 0xff}: Green,
|
||||
{0x55, 0x8b, 0x2f, 0xff}: Green,
|
||||
{0x33, 0x69, 0x1e, 0xff}: Green,
|
||||
{0xb9, 0xf6, 0xca, 0xff}: Green,
|
||||
{0x69, 0xf0, 0xae, 0xff}: Green,
|
||||
{0x00, 0xc8, 0x53, 0xff}: Green,
|
||||
{0x00, 0xe6, 0x76, 0xff}: Green,
|
||||
{0xcc, 0xff, 0x90, 0xff}: Green,
|
||||
{0xb2, 0xff, 0x59, 0xff}: Green,
|
||||
{0x76, 0xff, 0x03, 0xff}: Green,
|
||||
{0x64, 0xdd, 0x17, 0xff}: Green,
|
||||
{0xdd, 0xd5, 0x79, 0xff}: Green,
|
||||
{0xee, 0xec, 0xa2, 0xff}: Green,
|
||||
{0x24, 0x4e, 0x3b, 0xff}: Green,
|
||||
{0x9a, 0x9d, 0x47, 0xff}: Green,
|
||||
{0xbe, 0xbd, 0x76, 0xff}: Green,
|
||||
{0x5c, 0x5a, 0x30, 0xff}: Green,
|
||||
{0xb3, 0xc1, 0x6c, 0xff}: Green,
|
||||
{0xac, 0xa7, 0x83, 0xff}: Green,
|
||||
{0x47, 0x4c, 0x25, 0xff}: Green,
|
||||
{0xcd, 0xd0, 0x87, 0xff}: Green,
|
||||
{0x79, 0x6d, 0x41, 0xff}: Green,
|
||||
{0xf0, 0xf4, 0xc3, 0xff}: Lime,
|
||||
{0xe6, 0xee, 0x9c, 0xff}: Lime,
|
||||
{0xdc, 0xe7, 0x75, 0xff}: Lime,
|
||||
{0xd4, 0xe1, 0x57, 0xff}: Lime,
|
||||
{0xCD, 0xDC, 0x39, 0xff}: Lime,
|
||||
{0xc0, 0xca, 0x33, 0xff}: Lime,
|
||||
{0xaf, 0xb4, 0x2b, 0xff}: Lime,
|
||||
{0xee, 0xff, 0x41, 0xff}: Lime,
|
||||
{0xc6, 0xff, 0x00, 0xff}: Lime,
|
||||
{0xae, 0xea, 0x00, 0xff}: Lime,
|
||||
{0xff, 0xf9, 0xc4, 0xff}: Yellow,
|
||||
{0xff, 0xf5, 0x9d, 0xff}: Yellow,
|
||||
{0xff, 0xf1, 0x76, 0xff}: Yellow,
|
||||
{0xff, 0xee, 0x58, 0xff}: Yellow,
|
||||
{0xff, 0xff, 0x8d, 0xff}: Yellow,
|
||||
{0xff, 0xff, 0x00, 0xff}: Yellow,
|
||||
{0xff, 0xd5, 0x4f, 0xff}: Yellow,
|
||||
{0xff, 0xca, 0x28, 0xff}: Yellow,
|
||||
{0xe3, 0xce, 0x81, 0xff}: Yellow,
|
||||
{0xd1, 0xaf, 0x52, 0xff}: Yellow,
|
||||
{0xee, 0xbb, 0x2b, 0xff}: Yellow,
|
||||
{0xd3, 0xa8, 0x3a, 0xff}: Yellow,
|
||||
{0xc5, 0xa7, 0x02, 0xff}: Yellow,
|
||||
{0x9f, 0x82, 0x01, 0xff}: Yellow,
|
||||
{0xe8, 0xce, 0x03, 0xff}: Yellow,
|
||||
{0xf9, 0xa8, 0x25, 0xff}: Orange,
|
||||
{0xFF, 0x98, 0x00, 0xff}: Orange,
|
||||
{0xff, 0xa7, 0x26, 0xff}: Orange,
|
||||
{0xfb, 0x8c, 0x00, 0xff}: Orange,
|
||||
{0xf5, 0x7c, 0x00, 0xff}: Orange,
|
||||
{0xef, 0x6c, 0x00, 0xff}: Orange,
|
||||
{0xff, 0x91, 0x00, 0xff}: Orange,
|
||||
{0xff, 0x6d, 0x00, 0xff}: Orange,
|
||||
{0xfd, 0x9a, 0x31, 0xff}: Orange,
|
||||
{0x7d, 0x27, 0x04, 0xff}: Orange,
|
||||
{0xfd, 0x57, 0x1f, 0xff}: Orange,
|
||||
{0xf8, 0x67, 0x04, 0xff}: Orange,
|
||||
{0xfd, 0x9a, 0x00, 0xff}: Orange,
|
||||
{0xfe, 0x8a, 0x00, 0xff}: Orange,
|
||||
{0xf1, 0x96, 0x52, 0xff}: Orange,
|
||||
{0xe5, 0x83, 0x47, 0xff}: Orange,
|
||||
{0xc9, 0x4c, 0x30, 0xff}: Orange,
|
||||
{0x9f, 0x56, 0x01, 0xff}: Orange,
|
||||
{0xfa, 0x68, 0x01, 0xff}: Orange,
|
||||
{0xbb, 0x72, 0x3d, 0xff}: Orange,
|
||||
{0xff, 0x52, 0x52, 0xff}: Red,
|
||||
{0xf4, 0x43, 0x36, 0xff}: Red,
|
||||
{0xef, 0x53, 0x50, 0xff}: Red,
|
||||
{0xe5, 0x39, 0x35, 0xff}: Red,
|
||||
{0xf6, 0x29, 0x2e, 0xff}: Red,
|
||||
{0xfc, 0x25, 0x2d, 0xff}: Red,
|
||||
{0xd3, 0x2f, 0x2f, 0xff}: Red,
|
||||
{0xc6, 0x28, 0x28, 0xff}: Red,
|
||||
{0xba, 0x28, 0x30, 0xff}: Red,
|
||||
{0xb7, 0x1c, 0x1c, 0xff}: Red,
|
||||
{0xd5, 0x00, 0x00, 0xff}: Red,
|
||||
{0xdb, 0x08, 0x06, 0xff}: Red,
|
||||
{0xcf, 0x09, 0x04, 0xff}: Red,
|
||||
{0xd8, 0x1a, 0x14, 0xff}: Red,
|
||||
{0xcc, 0x17, 0x08, 0xff}: Red,
|
||||
{0xd8, 0x0a, 0x07, 0xff}: Red,
|
||||
{0xde, 0x26, 0x16, 0xff}: Red,
|
||||
{0xee, 0x24, 0x0f, 0xff}: Red,
|
||||
{0xa1, 0x21, 0x1f, 0xff}: Red,
|
||||
{0x70, 0x12, 0x19, 0xff}: Red,
|
||||
{0x51, 0x12, 0x18, 0xff}: Red,
|
||||
{0x49, 0x11, 0x14, 0xff}: Red,
|
||||
{0xfc, 0xe4, 0xec, 0xff}: Pink,
|
||||
{0xfd, 0xc8, 0xeb, 0xff}: Pink,
|
||||
{0xe7, 0x9f, 0xa6, 0xff}: Pink,
|
||||
{0xf8, 0xbb, 0xd0, 0xff}: Pink,
|
||||
{0xf4, 0x8f, 0xb1, 0xff}: Pink,
|
||||
{0xff, 0x80, 0xab, 0xff}: Pink,
|
||||
{0xff, 0x40, 0x81, 0xff}: Pink,
|
||||
{0xf5, 0x00, 0x57, 0xff}: Pink,
|
||||
{0xf0, 0x62, 0x92, 0xff}: Pink,
|
||||
{0xec, 0x40, 0x7a, 0xff}: Pink,
|
||||
{0xe9, 0x1e, 0x63, 0xff}: Pink,
|
||||
{0xd8, 0x1b, 0x60, 0xff}: Pink,
|
||||
{0xc2, 0x18, 0x5b, 0xff}: Pink,
|
||||
{0xff, 0x00, 0xff, 0xff}: Magenta,
|
||||
{0xe5, 0x00, 0xe5, 0xff}: Magenta,
|
||||
{0xf0, 0x00, 0xb5, 0xff}: Magenta,
|
||||
{0xce, 0x00, 0x9b, 0xff}: Magenta,
|
||||
{0xc0, 0x05, 0x5b, 0xff}: Magenta,
|
||||
{0xb0, 0x00, 0x85, 0xff}: Magenta,
|
||||
{0xa8, 0x28, 0x63, 0xff}: Magenta,
|
||||
{0x5b, 0x00, 0x2f, 0xff}: Magenta,
|
||||
{0x4b, 0x01, 0x21, 0xff}: Magenta,
|
||||
{0x86, 0x02, 0x25, 0xff}: Magenta,
|
||||
{0xcb, 0x02, 0x3d, 0xff}: Magenta,
|
||||
{0x64, 0x07, 0x1a, 0xff}: Magenta,
|
||||
{0x9e, 0x00, 0x47, 0xff}: Magenta,
|
||||
{0xdc, 0x7a, 0xcf, 0xff}: Magenta,
|
||||
{0xed, 0xde, 0xac, 0xff}: Gold,
|
||||
{0xe8, 0xb4, 0x51, 0xff}: Gold,
|
||||
{0xc0, 0x8a, 0x3e, 0xff}: Gold,
|
||||
{0xa2, 0x7d, 0x4b, 0xff}: Gold,
|
||||
{0x75, 0x55, 0x31, 0xff}: Gold,
|
||||
{0xd1, 0x93, 0x27, 0xff}: Gold,
|
||||
{0xde, 0xa2, 0x53, 0xff}: Gold,
|
||||
{0xd5, 0xaa, 0x6f, 0xff}: Gold,
|
||||
{0xf5, 0xea, 0xd4, 0xff}: Gold,
|
||||
}
|
||||
|
||||
func Colorful(actualColor colorful.Color) (result Color) {
|
||||
var distance = 1.0
|
||||
|
||||
for rgba, i := range ColorMap {
|
||||
colorColorful, _ := colorful.MakeColor(rgba)
|
||||
currentDistance := colorColorful.DistanceLab(actualColor)
|
||||
|
||||
if distance >= currentDistance {
|
||||
distance = currentDistance
|
||||
result = i
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
11
internal/colors/colors_test.go
Normal file
11
internal/colors/colors_test.go
Normal file
|
@ -0,0 +1,11 @@
|
|||
package colors
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestColors_List(t *testing.T) {
|
||||
allColors := All.List()
|
||||
|
||||
t.Logf("colors: %+v", allColors)
|
||||
}
|
|
@ -6,12 +6,14 @@ import (
|
|||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jinzhu/gorm"
|
||||
_ "github.com/jinzhu/gorm/dialects/mysql"
|
||||
_ "github.com/jinzhu/gorm/dialects/sqlite"
|
||||
gc "github.com/patrickmn/go-cache"
|
||||
"github.com/photoprism/photoprism/internal/colors"
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/tidb"
|
||||
|
@ -549,6 +551,7 @@ func (c *Config) MigrateDb() {
|
|||
&entity.File{},
|
||||
&entity.Photo{},
|
||||
&entity.Event{},
|
||||
&entity.Place{},
|
||||
&entity.Location{},
|
||||
&entity.Camera{},
|
||||
&entity.Lens{},
|
||||
|
@ -563,6 +566,9 @@ func (c *Config) MigrateDb() {
|
|||
&entity.Keyword{},
|
||||
&entity.PhotoKeyword{},
|
||||
)
|
||||
|
||||
entity.CreateUnknownPlace(db)
|
||||
entity.CreateUnknownCountry(db)
|
||||
}
|
||||
|
||||
// ClientConfig returns a loaded and set configuration entity.
|
||||
|
@ -570,6 +576,7 @@ func (c *Config) ClientConfig() ClientConfig {
|
|||
db := c.Db()
|
||||
|
||||
var cameras []*entity.Camera
|
||||
var lenses []*entity.Lens
|
||||
var albums []*entity.Album
|
||||
|
||||
var position struct {
|
||||
|
@ -614,11 +621,11 @@ func (c *Config) ClientConfig() ClientConfig {
|
|||
Take(&count)
|
||||
|
||||
db.Table("countries").
|
||||
Select("COUNT(*) AS countries").
|
||||
Select("(COUNT(*) - 1) AS countries").
|
||||
Take(&count)
|
||||
|
||||
db.Table("locations").
|
||||
Select("COUNT(DISTINCT loc_place) AS places").
|
||||
db.Table("places").
|
||||
Select("(COUNT(*) - 1) AS places").
|
||||
Take(&count)
|
||||
|
||||
type country struct {
|
||||
|
@ -637,10 +644,39 @@ func (c *Config) ClientConfig() ClientConfig {
|
|||
Limit(1000).Order("camera_model").
|
||||
Find(&cameras)
|
||||
|
||||
db.Where("deleted_at IS NULL").
|
||||
Limit(1000).Order("lens_model").
|
||||
Find(&lenses)
|
||||
|
||||
db.Where("deleted_at IS NULL AND album_favorite = 1").
|
||||
Limit(20).Order("album_name").
|
||||
Find(&albums)
|
||||
|
||||
var years []string
|
||||
|
||||
db.Table("photos").
|
||||
Order("photo_year DESC").
|
||||
Pluck("DISTINCT photo_year", &years)
|
||||
|
||||
type CategoryLabel struct {
|
||||
LabelName string
|
||||
Title string
|
||||
}
|
||||
|
||||
var categories []CategoryLabel
|
||||
|
||||
db.Table("categories").
|
||||
Select("l.label_name").
|
||||
Joins("JOIN labels l ON categories.category_id = l.id").
|
||||
Group("l.label_name").
|
||||
Order("l.label_name").
|
||||
Limit(1000).Offset(0).
|
||||
Scan(&categories)
|
||||
|
||||
for i, l := range categories {
|
||||
categories[i].Title = strings.Title(l.LabelName)
|
||||
}
|
||||
|
||||
jsHash := util.Hash(c.HttpStaticBuildPath() + "/app.js")
|
||||
cssHash := util.Hash(c.HttpStaticBuildPath() + "/app.css")
|
||||
|
||||
|
@ -660,6 +696,7 @@ func (c *Config) ClientConfig() ClientConfig {
|
|||
"public": c.Public(),
|
||||
"albums": albums,
|
||||
"cameras": cameras,
|
||||
"lenses": lenses,
|
||||
"countries": countries,
|
||||
"thumbnails": Thumbnails,
|
||||
"jsHash": jsHash,
|
||||
|
@ -667,6 +704,9 @@ func (c *Config) ClientConfig() ClientConfig {
|
|||
"settings": c.Settings(),
|
||||
"count": count,
|
||||
"pos": position,
|
||||
"years": years,
|
||||
"colors": colors.All.List(),
|
||||
"categories": categories,
|
||||
}
|
||||
|
||||
return result
|
||||
|
|
|
@ -3,6 +3,7 @@ package entity
|
|||
import (
|
||||
"github.com/gosimple/slug"
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/photoprism/photoprism/internal/maps"
|
||||
)
|
||||
|
||||
var altCountryNames = map[string]string{
|
||||
|
@ -22,6 +23,12 @@ type Country struct {
|
|||
New bool `gorm:"-"`
|
||||
}
|
||||
|
||||
var UnknownCountry = NewCountry("zz", maps.CountryNames["zz"])
|
||||
|
||||
func CreateUnknownCountry(db *gorm.DB) {
|
||||
UnknownCountry.FirstOrCreate(db)
|
||||
}
|
||||
|
||||
// Create a new country
|
||||
func NewCountry(countryCode string, countryName string) *Country {
|
||||
if countryCode == "" {
|
||||
|
@ -54,3 +61,11 @@ func (m *Country) FirstOrCreate(db *gorm.DB) *Country {
|
|||
func (m *Country) AfterCreate(scope *gorm.Scope) error {
|
||||
return scope.SetColumn("New", true)
|
||||
}
|
||||
|
||||
func (m *Country) Code() string {
|
||||
return m.ID
|
||||
}
|
||||
|
||||
func (m *Country) Name() string {
|
||||
return m.CountryName
|
||||
}
|
||||
|
|
|
@ -12,18 +12,14 @@ import (
|
|||
// Photo location
|
||||
type Location struct {
|
||||
ID uint64 `gorm:"type:BIGINT;primary_key;auto_increment:false;"`
|
||||
PlaceID uint64 `gorm:"type:BIGINT;"`
|
||||
Place *Place
|
||||
LocLat float64
|
||||
LocLng float64
|
||||
LocName string `gorm:"type:varchar(100);"`
|
||||
LocCategory string `gorm:"type:varchar(50);"`
|
||||
LocSuburb string `gorm:"type:varchar(100);"`
|
||||
LocPlace string `gorm:"type:varbinary(500);index;"`
|
||||
LocCity string `gorm:"type:varchar(100);"`
|
||||
LocState string `gorm:"type:varchar(100);"`
|
||||
LocCountry string `gorm:"type:binary(2);"`
|
||||
LocSource string `gorm:"type:varbinary(16);"`
|
||||
LocNotes string `gorm:"type:text;"`
|
||||
LocFavorite bool
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
@ -40,6 +36,7 @@ func NewLocation(lat, lng float64) *Location {
|
|||
|
||||
func (m *Location) Find(db *gorm.DB) error {
|
||||
if err := db.First(m, "id = ?", m.ID).Error; err == nil {
|
||||
m.Place = FindPlace(m.PlaceID, db)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -53,13 +50,19 @@ func (m *Location) Find(db *gorm.DB) error {
|
|||
return err
|
||||
}
|
||||
|
||||
m.Place = FindPlaceByLabel(l.ID, l.LocLabel, db)
|
||||
|
||||
if m.Place.NoID() {
|
||||
m.Place.ID = l.ID
|
||||
m.Place.LocLabel = l.LocLabel
|
||||
m.Place.LocCity = l.LocCity
|
||||
m.Place.LocState = l.LocState
|
||||
m.Place.LocCountry = l.LocCountry
|
||||
}
|
||||
|
||||
m.LocName = l.LocName
|
||||
m.LocCategory = l.LocCategory
|
||||
m.LocSuburb = l.LocSuburb
|
||||
m.LocPlace = l.LocPlace
|
||||
m.LocCity = l.LocCity
|
||||
m.LocState = l.LocState
|
||||
m.LocCountry = l.LocCountry
|
||||
m.LocSource = l.LocSource
|
||||
|
||||
if err := db.Create(m).Error; err != nil {
|
||||
|
@ -72,16 +75,16 @@ func (m *Location) Find(db *gorm.DB) error {
|
|||
|
||||
func (m *Location) Keywords() []string {
|
||||
result := []string{
|
||||
strings.ToLower(m.LocCity),
|
||||
strings.ToLower(m.LocSuburb),
|
||||
strings.ToLower(m.LocState),
|
||||
strings.ToLower(m.City()),
|
||||
strings.ToLower(m.Suburb()),
|
||||
strings.ToLower(m.State()),
|
||||
strings.ToLower(m.CountryName()),
|
||||
strings.ToLower(m.LocCategory),
|
||||
strings.ToLower(m.Category()),
|
||||
}
|
||||
|
||||
result = append(result, util.Keywords(m.LocName)...)
|
||||
result = append(result, util.Keywords(m.LocPlace)...)
|
||||
result = append(result, util.Keywords(m.LocNotes)...)
|
||||
result = append(result, util.Keywords(m.Name())...)
|
||||
result = append(result, util.Keywords(m.Label())...)
|
||||
result = append(result, util.Keywords(m.Notes())...)
|
||||
|
||||
return result
|
||||
}
|
||||
|
@ -102,32 +105,64 @@ func (m *Location) Name() string {
|
|||
return m.LocName
|
||||
}
|
||||
|
||||
func (m *Location) NoName() bool {
|
||||
return m.LocName == ""
|
||||
}
|
||||
|
||||
func (m *Location) Category() string {
|
||||
return m.LocCategory
|
||||
}
|
||||
|
||||
func (m *Location) NoCategory() bool {
|
||||
return m.LocCategory == ""
|
||||
}
|
||||
|
||||
func (m *Location) Suburb() string {
|
||||
return m.LocSuburb
|
||||
}
|
||||
|
||||
func (m *Location) Place() string {
|
||||
return m.LocPlace
|
||||
func (m *Location) NoSuburb() bool {
|
||||
return m.LocSuburb == ""
|
||||
}
|
||||
|
||||
func (m *Location) Label() string {
|
||||
return m.Place.Label()
|
||||
}
|
||||
|
||||
func (m *Location) City() string {
|
||||
return m.LocCity
|
||||
return m.Place.City()
|
||||
}
|
||||
|
||||
func (m *Location) LongCity() bool {
|
||||
return len(m.City()) > 16
|
||||
}
|
||||
|
||||
func (m *Location) NoCity() bool {
|
||||
return m.City() == ""
|
||||
}
|
||||
|
||||
func (m *Location) CityContains(text string) bool {
|
||||
return strings.Contains(text, m.City())
|
||||
}
|
||||
|
||||
func (m *Location) State() string {
|
||||
return m.LocState
|
||||
return m.Place.State()
|
||||
}
|
||||
|
||||
func (m *Location) NoState() bool {
|
||||
return m.Place.State() == ""
|
||||
}
|
||||
|
||||
func (m *Location) CountryCode() string {
|
||||
return m.LocCountry
|
||||
return m.Place.CountryCode()
|
||||
}
|
||||
|
||||
func (m *Location) CountryName() string {
|
||||
return maps.CountryNames[m.LocCountry]
|
||||
return m.Place.CountryName()
|
||||
}
|
||||
|
||||
func (m *Location) Notes() string {
|
||||
return m.Place.Notes()
|
||||
}
|
||||
|
||||
func (m *Location) Source() string {
|
||||
|
|
|
@ -32,16 +32,19 @@ type Photo struct {
|
|||
PhotoExposure string `gorm:"type:varbinary(16);"`
|
||||
PhotoViews uint
|
||||
Camera *Camera
|
||||
CameraID uint `gorm:"index;"`
|
||||
CameraID uint `gorm:"index:idx_photos_camera_lens;"`
|
||||
Lens *Lens
|
||||
LensID uint `gorm:"index;"`
|
||||
Country *Country
|
||||
CountryID string `gorm:"type:binary(2);index;"`
|
||||
LensID uint `gorm:"index:idx_photos_camera_lens;"`
|
||||
CountryChanged bool
|
||||
Location *Location
|
||||
LocationID uint64 `gorm:"type:BIGINT;index;"`
|
||||
Place *Place
|
||||
PlaceID uint64 `gorm:"type:BIGINT;index;"`
|
||||
LocationChanged bool
|
||||
LocationEstimated bool
|
||||
PhotoCountry string `gorm:"index:idx_photos_country_year_month;"`
|
||||
PhotoYear int `gorm:"index:idx_photos_country_year_month;"`
|
||||
PhotoMonth int `gorm:"index:idx_photos_country_year_month;"`
|
||||
TakenAt time.Time `gorm:"type:datetime;index;"`
|
||||
TakenAtLocal time.Time `gorm:"type:datetime;"`
|
||||
TakenAtChanged bool
|
||||
|
@ -146,7 +149,22 @@ func (m *Photo) NoLocation() bool {
|
|||
return m.LocationID == 0
|
||||
}
|
||||
|
||||
func (m *Photo) HasLocation() bool {
|
||||
return m.LocationID != 0
|
||||
}
|
||||
|
||||
func (m *Photo) NoPlace() bool {
|
||||
return m.PlaceID < 5
|
||||
}
|
||||
|
||||
func (m *Photo) HasPlace() bool {
|
||||
return m.PlaceID >= 5
|
||||
}
|
||||
|
||||
func (m *Photo) NoTitle() bool {
|
||||
return m.PhotoTitle == ""
|
||||
}
|
||||
|
||||
func (m *Photo) HasTitle() bool {
|
||||
return m.PhotoTitle != ""
|
||||
}
|
||||
|
|
108
internal/entity/place.go
Normal file
108
internal/entity/place.go
Normal file
|
@ -0,0 +1,108 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/photoprism/photoprism/internal/maps"
|
||||
)
|
||||
|
||||
// Photo place
|
||||
type Place struct {
|
||||
ID uint64 `gorm:"type:BIGINT;primary_key;auto_increment:false;"`
|
||||
LocLabel string `gorm:"type:varbinary(500);unique_index;"`
|
||||
LocCity string `gorm:"type:varchar(100);"`
|
||||
LocState string `gorm:"type:varchar(100);"`
|
||||
LocCountry string `gorm:"type:binary(2);"`
|
||||
LocNotes string `gorm:"type:text;"`
|
||||
LocFavorite bool
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
New bool `gorm:"-"`
|
||||
}
|
||||
|
||||
var UnknownPlace = NewPlace(1, "Unknown", "Unknown", "Unknown", "zz")
|
||||
|
||||
func CreateUnknownPlace(db *gorm.DB) {
|
||||
UnknownPlace.FirstOrCreate(db)
|
||||
}
|
||||
|
||||
func (m *Place) AfterCreate(scope *gorm.Scope) error {
|
||||
return scope.SetColumn("New", true)
|
||||
}
|
||||
|
||||
func FindPlace(id uint64, db *gorm.DB) *Place {
|
||||
place := &Place{}
|
||||
|
||||
if err := db.First(place, "id = ?", id).Error; err != nil {
|
||||
log.Debugf("place: %s for id %d", err.Error(), id)
|
||||
}
|
||||
|
||||
return place
|
||||
}
|
||||
|
||||
func FindPlaceByLabel(id uint64, label string, db *gorm.DB) *Place {
|
||||
place := &Place{}
|
||||
|
||||
if err := db.First(place, "id = ? OR loc_label = ?", id, label).Error; err != nil {
|
||||
log.Debugf("place: %s for id %d or label \"%s\"", err.Error(), id, label)
|
||||
}
|
||||
|
||||
return place
|
||||
}
|
||||
|
||||
func NewPlace(id uint64, label, city, state, countryCode string) *Place {
|
||||
result := &Place{
|
||||
ID: id,
|
||||
LocLabel: label,
|
||||
LocCity: city,
|
||||
LocState: state,
|
||||
LocCountry: countryCode,
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (m *Place) Find(db *gorm.DB) error {
|
||||
if err := db.First(m, "id = ?", m.ID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Place) FirstOrCreate(db *gorm.DB) *Place {
|
||||
if err := db.FirstOrCreate(m, "id = ? OR loc_label = ?", m.ID, m.LocLabel).Error; err != nil {
|
||||
log.Debugf("place: %s for id %d or label \"%s\"", err.Error(), m.ID, m.LocLabel)
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *Place) NoID() bool {
|
||||
return m.ID == 0
|
||||
}
|
||||
|
||||
func (m *Place) Label() string {
|
||||
return m.LocLabel
|
||||
}
|
||||
|
||||
func (m *Place) City() string {
|
||||
return m.LocCity
|
||||
}
|
||||
|
||||
func (m *Place) State() string {
|
||||
return m.LocState
|
||||
}
|
||||
|
||||
func (m *Place) CountryCode() string {
|
||||
return m.LocCountry
|
||||
}
|
||||
|
||||
func (m *Place) CountryName() string {
|
||||
return maps.CountryNames[m.LocCountry]
|
||||
}
|
||||
|
||||
func (m *Place) Notes() string {
|
||||
return m.LocNotes
|
||||
}
|
|
@ -251,4 +251,5 @@ var CountryNames = map[string]string{
|
|||
"ye": "Yemen",
|
||||
"zm": "Zambia",
|
||||
"zw": "Zimbabwe",
|
||||
}
|
||||
"zz": "Unknown",
|
||||
}
|
|
@ -994,5 +994,9 @@
|
|||
{
|
||||
"Code": "ZW",
|
||||
"Name": "Zimbabwe"
|
||||
},
|
||||
{
|
||||
"Code": "ZZ",
|
||||
"Name": "Unknown"
|
||||
}
|
||||
]
|
||||
|
|
|
@ -7,6 +7,21 @@ import (
|
|||
"github.com/photoprism/photoprism/internal/maps/osm"
|
||||
)
|
||||
|
||||
/* TODO
|
||||
|
||||
(SELECT pl.loc_label as album_name, pl.loc_country, YEAR(ph.taken_at) as taken_year, round(count(ph.id)) as photo_count FROM photos ph
|
||||
JOIN places pl ON ph.place_id = pl.id AND pl.id <> 1
|
||||
GROUP BY album_name, taken_year HAVING photo_count > 5) UNION (
|
||||
SELECT c.country_name AS album_name, pl.loc_country, YEAR(ph.taken_at) as taken_year, round(count(ph.id)) as photo_count FROM photos ph
|
||||
JOIN places pl ON ph.place_id = pl.id AND pl.id <> 1
|
||||
JOIN countries c ON c.id = pl.loc_country
|
||||
GROUP BY album_name, taken_year
|
||||
HAVING photo_count > 10)
|
||||
ORDER BY loc_country, album_name, taken_year;
|
||||
|
||||
*/
|
||||
|
||||
|
||||
// Photo location
|
||||
type Location struct {
|
||||
ID uint64
|
||||
|
@ -15,7 +30,7 @@ type Location struct {
|
|||
LocName string
|
||||
LocCategory string
|
||||
LocSuburb string
|
||||
LocPlace string
|
||||
LocLabel string
|
||||
LocCity string
|
||||
LocState string
|
||||
LocCountry string
|
||||
|
@ -81,7 +96,7 @@ func (l *Location) Assign(s LocationSource) error {
|
|||
l.LocState = s.State()
|
||||
l.LocCountry = s.CountryCode()
|
||||
l.LocCategory = s.Category()
|
||||
l.LocPlace = l.place()
|
||||
l.LocLabel = l.label()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -94,7 +109,7 @@ func (l *Location) Unknown() bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func (l *Location) place() string {
|
||||
func (l *Location) label() string {
|
||||
if l.Unknown() {
|
||||
return "Unknown"
|
||||
}
|
||||
|
@ -103,9 +118,8 @@ func (l *Location) place() string {
|
|||
var loc []string
|
||||
|
||||
shortCountry := len([]rune(countryName)) <= 20
|
||||
shortCity := len([]rune(l.LocCity)) <= 20
|
||||
|
||||
if shortCity && l.LocCity != "" {
|
||||
if l.LocCity != "" {
|
||||
loc = append(loc, l.LocCity)
|
||||
}
|
||||
|
||||
|
@ -140,8 +154,8 @@ func (l Location) Suburb() string {
|
|||
return l.LocSuburb
|
||||
}
|
||||
|
||||
func (l Location) Place() string {
|
||||
return l.LocPlace
|
||||
func (l Location) Label() string {
|
||||
return l.LocLabel
|
||||
}
|
||||
|
||||
func (l Location) City() string {
|
||||
|
|
|
@ -19,7 +19,7 @@ func TestLocation_Query(t *testing.T) {
|
|||
}
|
||||
|
||||
assert.Equal(t, "Fernsehturm Berlin", l.LocName)
|
||||
assert.Equal(t, "Berlin, Germany", l.LocPlace)
|
||||
assert.Equal(t, "Berlin, Germany", l.LocLabel)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -48,7 +48,7 @@ func TestLocation_Assign(t *testing.T) {
|
|||
}
|
||||
|
||||
assert.Equal(t, "Fernsehturm Berlin", l.LocName)
|
||||
assert.Equal(t, "Berlin, Germany", l.LocPlace)
|
||||
assert.Equal(t, "Berlin, Germany", l.LocLabel)
|
||||
})
|
||||
|
||||
t.Run("SantaMonica", func(t *testing.T) {
|
||||
|
@ -76,7 +76,7 @@ func TestLocation_Assign(t *testing.T) {
|
|||
}
|
||||
|
||||
assert.Equal(t, "Santa Monica Pier", l.LocName)
|
||||
assert.Equal(t, "Santa Monica, California, USA", l.LocPlace)
|
||||
assert.Equal(t, "Santa Monica, California, USA", l.LocLabel)
|
||||
})
|
||||
|
||||
t.Run("AirportZurich", func(t *testing.T) {
|
||||
|
@ -105,7 +105,7 @@ func TestLocation_Assign(t *testing.T) {
|
|||
}
|
||||
|
||||
assert.Equal(t, "Airport", l.LocName)
|
||||
assert.Equal(t, "Kloten, Zurich, Switzerland", l.LocPlace)
|
||||
assert.Equal(t, "Kloten, Zurich, Switzerland", l.LocLabel)
|
||||
})
|
||||
|
||||
t.Run("AirportTegel", func(t *testing.T) {
|
||||
|
@ -134,7 +134,7 @@ func TestLocation_Assign(t *testing.T) {
|
|||
}
|
||||
|
||||
assert.Equal(t, "Airport", l.LocName)
|
||||
assert.Equal(t, "Berlin, Germany", l.LocPlace)
|
||||
assert.Equal(t, "Berlin, Germany", l.LocLabel)
|
||||
})
|
||||
|
||||
t.Run("PinkBeach", func(t *testing.T) {
|
||||
|
@ -164,7 +164,7 @@ func TestLocation_Assign(t *testing.T) {
|
|||
|
||||
assert.Equal(t, uint64(0x149ce78540000000), l.ID)
|
||||
assert.Equal(t, "Pink Beach", l.LocName)
|
||||
assert.Equal(t, "Chrisoskalitissa, Crete, Greece", l.LocPlace)
|
||||
assert.Equal(t, "Chrisoskalitissa, Crete, Greece", l.LocLabel)
|
||||
})
|
||||
|
||||
t.Run("NewJersey", func(t *testing.T) {
|
||||
|
@ -194,7 +194,7 @@ func TestLocation_Assign(t *testing.T) {
|
|||
|
||||
assert.Equal(t, uint64(0x9c25741c0000000), l.ID)
|
||||
assert.Equal(t, "", l.LocName)
|
||||
assert.Equal(t, "Jersey City, New Jersey, USA", l.LocPlace)
|
||||
assert.Equal(t, "Jersey City, New Jersey, USA", l.LocLabel)
|
||||
})
|
||||
|
||||
t.Run("SouthAfrica", func(t *testing.T) {
|
||||
|
@ -224,7 +224,7 @@ func TestLocation_Assign(t *testing.T) {
|
|||
|
||||
assert.Equal(t, uint64(0x1e5e4205c0000000), l.ID)
|
||||
assert.Equal(t, "R411", l.LocName)
|
||||
assert.Equal(t, "Eastern Cape, South Africa", l.LocPlace)
|
||||
assert.Equal(t, "Eastern Cape, South Africa", l.LocLabel)
|
||||
})
|
||||
|
||||
t.Run("Unknown", func(t *testing.T) {
|
||||
|
@ -272,7 +272,7 @@ func TestLocation_place(t *testing.T) {
|
|||
|
||||
l := NewLocation(lat, lng)
|
||||
|
||||
assert.Equal(t, "Unknown", l.place())
|
||||
assert.Equal(t, "Unknown", l.label())
|
||||
})
|
||||
t.Run("Nürnberg, Bayern, Germany", func(t *testing.T) {
|
||||
lat := -31.976301666666668
|
||||
|
@ -280,7 +280,7 @@ func TestLocation_place(t *testing.T) {
|
|||
|
||||
l := &Location{LocLat: lat, LocLng: lng, LocCountry: "de", LocCity: "Nürnberg", LocState: "Bayern"}
|
||||
|
||||
assert.Equal(t, "Nürnberg, Bayern, Germany", l.place())
|
||||
assert.Equal(t, "Nürnberg, Bayern, Germany", l.label())
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -373,13 +373,13 @@ func TestLocation_Source(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestLocation_Place(t *testing.T) {
|
||||
t.Run("test-place", func(t *testing.T) {
|
||||
t.Run("test-label", func(t *testing.T) {
|
||||
lat := -31.976301666666668
|
||||
lng := 29.148046666666666
|
||||
|
||||
l := &Location{LocCategory: "test", LocLat: lat, LocLng: lng, LocCountry: "de", LocCity: "Nürnberg", LocPlace: "test-place", LocState: "Bayern", LocName: "Christkindlesmarkt", LocSuburb: "Hauptmarkt"}
|
||||
l := &Location{LocCategory: "test", LocLat: lat, LocLng: lng, LocCountry: "de", LocCity: "Nürnberg", LocLabel: "test-label", LocState: "Bayern", LocName: "Christkindlesmarkt", LocSuburb: "Hauptmarkt"}
|
||||
|
||||
assert.Equal(t, "test-place", l.Place())
|
||||
assert.Equal(t, "test-label", l.Label())
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -388,7 +388,7 @@ func TestLocation_CountryCode(t *testing.T) {
|
|||
lat := -31.976301666666668
|
||||
lng := 29.148046666666666
|
||||
|
||||
l := &Location{LocCategory: "test", LocLat: lat, LocLng: lng, LocCountry: "de", LocCity: "Nürnberg", LocPlace: "test-place", LocState: "Bayern", LocName: "Christkindlesmarkt", LocSuburb: "Hauptmarkt"}
|
||||
l := &Location{LocCategory: "test", LocLat: lat, LocLng: lng, LocCountry: "de", LocCity: "Nürnberg", LocLabel: "test-label", LocState: "Bayern", LocName: "Christkindlesmarkt", LocSuburb: "Hauptmarkt"}
|
||||
|
||||
assert.Equal(t, "de", l.CountryCode())
|
||||
})
|
||||
|
@ -399,7 +399,7 @@ func TestLocation_CountryName(t *testing.T) {
|
|||
lat := -31.976301666666668
|
||||
lng := 29.148046666666666
|
||||
|
||||
l := &Location{LocCategory: "test", LocLat: lat, LocLng: lng, LocCountry: "de", LocCity: "Nürnberg", LocPlace: "test-place", LocState: "Bayern", LocName: "Christkindlesmarkt", LocSuburb: "Hauptmarkt"}
|
||||
l := &Location{LocCategory: "test", LocLat: lat, LocLng: lng, LocCountry: "de", LocCity: "Nürnberg", LocLabel: "test-label", LocState: "Bayern", LocName: "Christkindlesmarkt", LocSuburb: "Hauptmarkt"}
|
||||
|
||||
assert.Equal(t, "Germany", l.CountryName())
|
||||
})
|
||||
|
|
|
@ -85,6 +85,10 @@ func (o Location) City() (result string) {
|
|||
result = o.Address.State
|
||||
}
|
||||
|
||||
if len([]rune(result)) > 19 {
|
||||
result = ""
|
||||
}
|
||||
|
||||
return strings.TrimSpace(result)
|
||||
}
|
||||
|
||||
|
|
|
@ -86,7 +86,7 @@ func TestOSM_State(t *testing.T) {
|
|||
assert.Equal(t, "Berlin", l.State())
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
func TestOSM_City(t *testing.T) {
|
||||
t.Run("Berlin", func(t *testing.T) {
|
||||
|
||||
|
@ -113,13 +113,12 @@ func TestOSM_City(t *testing.T) {
|
|||
assert.Equal(t, "Wiesbaden", l.City())
|
||||
})
|
||||
t.Run("Frankfurt", func(t *testing.T) {
|
||||
|
||||
a := Address{CountryCode: "de", City: "", State: "Frankfurt", HouseNumber: "63", Suburb: "Neukölln", Town: "", Village: "", County: ""}
|
||||
a := Address{CountryCode: "de", City: "Frankfurt", State: "", HouseNumber: "63", Suburb: "Neukölln", Town: "", Village: "", County: ""}
|
||||
l := &Location{LocCategory: "natural", LocLat: "52.5208", LocLng: "13.40953", LocName: "Nice title", LocType: "hill", LocDisplayName: "dipslay name", Address: a}
|
||||
assert.Equal(t, "Frankfurt", l.City())
|
||||
})
|
||||
}
|
||||
|
||||
*/
|
||||
func TestOSM_Suburb(t *testing.T) {
|
||||
t.Run("Neukölln", func(t *testing.T) {
|
||||
|
||||
|
|
|
@ -2,413 +2,15 @@ package photoprism
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"image/color"
|
||||
"math"
|
||||
|
||||
"github.com/lucasb-eyer/go-colorful"
|
||||
"github.com/photoprism/photoprism/internal/colors"
|
||||
)
|
||||
|
||||
type ColorPerception struct {
|
||||
Colors IndexedColors
|
||||
MainColor IndexedColor
|
||||
Luminance LightMap
|
||||
Chroma Chroma
|
||||
}
|
||||
|
||||
type IndexedColor uint16
|
||||
type IndexedColors []IndexedColor
|
||||
|
||||
type Chroma uint8
|
||||
type Luminance uint8
|
||||
type LightMap []Luminance
|
||||
|
||||
const (
|
||||
Black IndexedColor = iota
|
||||
Brown
|
||||
Grey
|
||||
White
|
||||
Purple
|
||||
Gold
|
||||
Blue
|
||||
Cyan
|
||||
Teal
|
||||
Green
|
||||
Lime
|
||||
Yellow
|
||||
Magenta
|
||||
Orange
|
||||
Red
|
||||
Pink
|
||||
)
|
||||
|
||||
var IndexedColorNames = map[IndexedColor]string{
|
||||
Black: "dark", // 0
|
||||
Brown: "brown", // 1
|
||||
Grey: "grey", // 2
|
||||
White: "bright", // 3
|
||||
Purple: "purple", // 4
|
||||
Gold: "gold", // 5
|
||||
Blue: "blue", // 6
|
||||
Cyan: "cyan", // 7
|
||||
Teal: "teal", // 8
|
||||
Green: "green", // 9
|
||||
Lime: "lime", // A
|
||||
Yellow: "yellow", // B
|
||||
Magenta: "magenta", // C
|
||||
Orange: "orange", // D
|
||||
Red: "red", // E
|
||||
Pink: "pink", // F
|
||||
}
|
||||
|
||||
var IndexedColorWeight = map[IndexedColor]uint16{
|
||||
Black: 2,
|
||||
Brown: 2,
|
||||
Grey: 1,
|
||||
White: 2,
|
||||
Purple: 4,
|
||||
Gold: 4,
|
||||
Blue: 3,
|
||||
Cyan: 4,
|
||||
Teal: 4,
|
||||
Green: 3,
|
||||
Lime: 5,
|
||||
Yellow: 5,
|
||||
Magenta: 5,
|
||||
Orange: 4,
|
||||
Red: 4,
|
||||
Pink: 4,
|
||||
}
|
||||
|
||||
func (c IndexedColor) Name() string {
|
||||
return IndexedColorNames[c]
|
||||
}
|
||||
|
||||
func (c IndexedColor) Hex() string {
|
||||
return fmt.Sprintf("%X", c)
|
||||
}
|
||||
|
||||
func (c IndexedColors) Hex() (result string) {
|
||||
for _, indexedColor := range c {
|
||||
result += indexedColor.Hex()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (c Chroma) Hex() string {
|
||||
return fmt.Sprintf("%X", c)
|
||||
}
|
||||
|
||||
func (c Chroma) Uint() uint {
|
||||
return uint(c)
|
||||
}
|
||||
|
||||
func (c Chroma) Int() int {
|
||||
return int(c)
|
||||
}
|
||||
|
||||
func (l Luminance) Hex() string {
|
||||
return fmt.Sprintf("%X", l)
|
||||
}
|
||||
|
||||
func (m LightMap) Hex() (result string) {
|
||||
for _, luminance := range m {
|
||||
result += luminance.Hex()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
var IndexedColorMap = map[color.RGBA]IndexedColor{
|
||||
{0x00, 0x00, 0x00, 0xff}: Black,
|
||||
{0xa1, 0x88, 0x7f, 0xff}: Brown,
|
||||
{0x8d, 0x6e, 0x63, 0xff}: Brown,
|
||||
{0xa0, 0x7f, 0x6c, 0xff}: Brown,
|
||||
{0x9b, 0x7b, 0x5b, 0xff}: Brown,
|
||||
{0x75, 0x64, 0x5b, 0xff}: Brown,
|
||||
{0x79, 0x55, 0x48, 0xff}: Brown,
|
||||
{0x6d, 0x4c, 0x41, 0xff}: Brown,
|
||||
{0x5d, 0x40, 0x37, 0xff}: Brown,
|
||||
{0x9b, 0x61, 0x36, 0xff}: Brown,
|
||||
{0xc1, 0xa4, 0x87, 0xff}: Brown,
|
||||
{0xaa, 0x80, 0x62, 0xff}: Brown,
|
||||
{0x6b, 0x55, 0x46, 0xff}: Brown,
|
||||
{0xb4, 0xb5, 0x9c, 0xff}: Brown,
|
||||
{0xb2, 0xb4, 0x9b, 0xff}: Green,
|
||||
{0xe0, 0xe0, 0xe0, 0xff}: Grey,
|
||||
{0x9E, 0x9E, 0x9E, 0xff}: Grey,
|
||||
{0x75, 0x75, 0x75, 0xff}: Grey,
|
||||
{0x61, 0x61, 0x61, 0xff}: Grey,
|
||||
{0x42, 0x42, 0x42, 0xff}: Grey,
|
||||
{0x84, 0x7a, 0x72, 0xff}: Grey,
|
||||
{0xdf, 0xe0, 0xe1, 0xff}: Grey,
|
||||
{0xFF, 0xFF, 0xFF, 0xff}: White,
|
||||
{0xe4, 0xe4, 0xe4, 0xff}: White,
|
||||
{0xe7, 0xe7, 0xe7, 0xff}: White,
|
||||
{0xf3, 0xe5, 0xf5, 0xff}: Purple,
|
||||
{0xe1, 0xbe, 0xe7, 0xff}: Purple,
|
||||
{0xce, 0x93, 0xd8, 0xff}: Purple,
|
||||
{0xba, 0x68, 0xc8, 0xff}: Purple,
|
||||
{0xab, 0x47, 0xbc, 0xff}: Purple,
|
||||
{0x9c, 0x27, 0xb0, 0xff}: Purple,
|
||||
{0x9b, 0x31, 0x8f, 0xff}: Purple,
|
||||
{0x86, 0x00, 0x7e, 0xff}: Purple,
|
||||
{0x8e, 0x24, 0xaa, 0xff}: Purple,
|
||||
{0x7b, 0x1f, 0xa2, 0xff}: Purple,
|
||||
{0x6a, 0x1b, 0x9a, 0xff}: Purple,
|
||||
{0x4a, 0x14, 0x8c, 0xff}: Purple,
|
||||
{0xaa, 0x00, 0xff, 0xff}: Purple,
|
||||
{0xed, 0xe7, 0xf6, 0xff}: Purple,
|
||||
{0xd1, 0xc4, 0xe9, 0xff}: Purple,
|
||||
{0xb3, 0x9d, 0xdb, 0xff}: Purple,
|
||||
{0x95, 0x75, 0xcd, 0xff}: Purple,
|
||||
{0x7e, 0x57, 0xc2, 0xff}: Purple,
|
||||
{0x5e, 0x35, 0xb1, 0xff}: Purple,
|
||||
{0x67, 0x3a, 0xb7, 0xff}: Purple,
|
||||
{0x51, 0x2d, 0xa8, 0xff}: Purple,
|
||||
{0x45, 0x27, 0xa0, 0xff}: Purple,
|
||||
{0x31, 0x1b, 0x92, 0xff}: Purple,
|
||||
{0xb3, 0x88, 0xff, 0xff}: Purple,
|
||||
{0x7c, 0x4d, 0xff, 0xff}: Purple,
|
||||
{0x8e, 0x64, 0x93, 0xff}: Purple,
|
||||
{0x5e, 0x3a, 0x5e, 0xff}: Purple,
|
||||
{0x44, 0x0e, 0x79, 0xff}: Purple,
|
||||
{0x48, 0x36, 0x78, 0xff}: Purple,
|
||||
{0x4e, 0x38, 0x80, 0xff}: Purple,
|
||||
{0x3b, 0x0e, 0x79, 0xff}: Purple,
|
||||
{0x3F, 0x51, 0xB5, 0xff}: Blue,
|
||||
{0xc5, 0xca, 0xe9, 0xff}: Blue,
|
||||
{0x5c, 0x6b, 0xc0, 0xff}: Blue,
|
||||
{0x39, 0x49, 0xab, 0xff}: Blue,
|
||||
{0x30, 0x3f, 0x9f, 0xff}: Blue,
|
||||
{0x28, 0x35, 0x93, 0xff}: Blue,
|
||||
{0x1a, 0x23, 0x7e, 0xff}: Blue,
|
||||
{0x53, 0x6d, 0xfe, 0xff}: Blue,
|
||||
{0x3d, 0x5a, 0xfe, 0xff}: Blue,
|
||||
{0x30, 0x4f, 0xfe, 0xff}: Blue,
|
||||
{0x21, 0x96, 0xF3, 0xff}: Blue,
|
||||
{0xbb, 0xde, 0xfb, 0xff}: Blue,
|
||||
{0x90, 0xca, 0xf9, 0xff}: Blue,
|
||||
{0x64, 0xb5, 0xf6, 0xff}: Blue,
|
||||
{0x42, 0xa5, 0xf5, 0xff}: Blue,
|
||||
{0x1e, 0x88, 0xe5, 0xff}: Blue,
|
||||
{0x19, 0x76, 0xd2, 0xff}: Blue,
|
||||
{0x15, 0x65, 0xc0, 0xff}: Blue,
|
||||
{0x0d, 0x47, 0xa1, 0xff}: Blue,
|
||||
{0x82, 0xb1, 0xff, 0xff}: Blue,
|
||||
{0x44, 0x8a, 0xff, 0xff}: Blue,
|
||||
{0x29, 0x79, 0xff, 0xff}: Blue,
|
||||
{0x29, 0x62, 0xff, 0xff}: Blue,
|
||||
{0x03, 0xa9, 0xf6, 0xff}: Blue,
|
||||
{0xb3, 0xe5, 0xfc, 0xff}: Blue,
|
||||
{0x81, 0xd4, 0xfa, 0xff}: Blue,
|
||||
{0x4f, 0xc3, 0xf7, 0xff}: Blue,
|
||||
{0x29, 0xb6, 0xf6, 0xff}: Blue,
|
||||
{0x03, 0x9b, 0xe5, 0xff}: Blue,
|
||||
{0x02, 0x88, 0xd1, 0xff}: Blue,
|
||||
{0x02, 0x77, 0xbd, 0xff}: Blue,
|
||||
{0x01, 0x57, 0x9b, 0xff}: Blue,
|
||||
{0x80, 0xd8, 0xff, 0xff}: Blue,
|
||||
{0x40, 0xc4, 0xff, 0xff}: Blue,
|
||||
{0x00, 0xb0, 0xff, 0xff}: Blue,
|
||||
{0x00, 0x91, 0xea, 0xff}: Blue,
|
||||
{0x60, 0x7d, 0x8b, 0xff}: Blue,
|
||||
{0x78, 0x90, 0x9c, 0xff}: Blue,
|
||||
{0x54, 0x6e, 0x7a, 0xff}: Blue,
|
||||
{0x37, 0x47, 0x4f, 0xff}: Blue,
|
||||
{0xe4, 0xeb, 0xfd, 0xff}: Blue,
|
||||
{0x7d, 0xd3, 0xea, 0xff}: Blue,
|
||||
{0x07, 0x63, 0x99, 0xff}: Blue,
|
||||
{0x28, 0x44, 0x6b, 0xff}: Blue,
|
||||
{0x4a, 0xc8, 0xf5, 0xff}: Blue,
|
||||
{0x08, 0x00, 0xf4, 0xff}: Blue,
|
||||
{0x01, 0x2d, 0x5f, 0xff}: Blue,
|
||||
{0xb2, 0xeb, 0xf2, 0xff}: Cyan,
|
||||
{0x80, 0xde, 0xea, 0xff}: Cyan,
|
||||
{0x4d, 0xd0, 0xe1, 0xff}: Cyan,
|
||||
{0x26, 0xc6, 0xda, 0xff}: Cyan,
|
||||
{0x00, 0xb8, 0xd4, 0xff}: Cyan,
|
||||
{0x00, 0xBC, 0xD4, 0xff}: Cyan,
|
||||
{0x00, 0xac, 0xc1, 0xff}: Cyan,
|
||||
{0x00, 0x97, 0xa7, 0xff}: Cyan,
|
||||
{0x00, 0x83, 0x8f, 0xff}: Cyan,
|
||||
{0x00, 0x60, 0x64, 0xff}: Cyan,
|
||||
{0x84, 0xff, 0xff, 0xff}: Cyan,
|
||||
{0x18, 0xff, 0xff, 0xff}: Cyan,
|
||||
{0x00, 0xe5, 0xff, 0xff}: Cyan,
|
||||
{0x00, 0x96, 0x88, 0xff}: Teal,
|
||||
{0x00, 0x89, 0x7b, 0xff}: Teal,
|
||||
{0x00, 0x79, 0x6b, 0xff}: Teal,
|
||||
{0x00, 0x69, 0x5c, 0xff}: Teal,
|
||||
{0x04, 0x5d, 0x5c, 0xff}: Teal,
|
||||
{0x24, 0x5a, 0x5f, 0xff}: Teal,
|
||||
{0x03, 0x45, 0x4f, 0xff}: Teal,
|
||||
{0x2c, 0x54, 0x5e, 0xff}: Teal,
|
||||
{0x17, 0x47, 0x41, 0xff}: Teal,
|
||||
{0xe8, 0xf5, 0xe9, 0xff}: Green,
|
||||
{0xc8, 0xe6, 0xc9, 0xff}: Green,
|
||||
{0xab, 0xc7, 0xb0, 0xff}: Green,
|
||||
{0xa5, 0xd6, 0xa7, 0xff}: Green,
|
||||
{0x81, 0xc7, 0x84, 0xff}: Green,
|
||||
{0x66, 0xbb, 0x6a, 0xff}: Green,
|
||||
{0x4C, 0xAF, 0x50, 0xff}: Green,
|
||||
{0x43, 0xa0, 0x47, 0xff}: Green,
|
||||
{0x38, 0x8e, 0x3c, 0xff}: Green,
|
||||
{0x2e, 0x7d, 0x32, 0xff}: Green,
|
||||
{0x1b, 0x5e, 0x20, 0xff}: Green,
|
||||
{0xf1, 0xf8, 0xe9, 0xff}: Green,
|
||||
{0xdc, 0xed, 0xc8, 0xff}: Green,
|
||||
{0xc5, 0xe1, 0xa5, 0xff}: Green,
|
||||
{0xae, 0xd5, 0x81, 0xff}: Green,
|
||||
{0x8b, 0xc3, 0x4a, 0xff}: Green,
|
||||
{0x9c, 0xcc, 0x65, 0xff}: Green,
|
||||
{0x7c, 0xb3, 0x42, 0xff}: Green,
|
||||
{0x68, 0x9f, 0x38, 0xff}: Green,
|
||||
{0x55, 0x8b, 0x2f, 0xff}: Green,
|
||||
{0x33, 0x69, 0x1e, 0xff}: Green,
|
||||
{0xb9, 0xf6, 0xca, 0xff}: Green,
|
||||
{0x69, 0xf0, 0xae, 0xff}: Green,
|
||||
{0x00, 0xc8, 0x53, 0xff}: Green,
|
||||
{0x00, 0xe6, 0x76, 0xff}: Green,
|
||||
{0xcc, 0xff, 0x90, 0xff}: Green,
|
||||
{0xb2, 0xff, 0x59, 0xff}: Green,
|
||||
{0x76, 0xff, 0x03, 0xff}: Green,
|
||||
{0x64, 0xdd, 0x17, 0xff}: Green,
|
||||
{0xdd, 0xd5, 0x79, 0xff}: Green,
|
||||
{0xee, 0xec, 0xa2, 0xff}: Green,
|
||||
{0x24, 0x4e, 0x3b, 0xff}: Green,
|
||||
{0x9a, 0x9d, 0x47, 0xff}: Green,
|
||||
{0xbe, 0xbd, 0x76, 0xff}: Green,
|
||||
{0x5c, 0x5a, 0x30, 0xff}: Green,
|
||||
{0xb3, 0xc1, 0x6c, 0xff}: Green,
|
||||
{0xac, 0xa7, 0x83, 0xff}: Green,
|
||||
{0x47, 0x4c, 0x25, 0xff}: Green,
|
||||
{0xcd, 0xd0, 0x87, 0xff}: Green,
|
||||
{0x79, 0x6d, 0x41, 0xff}: Green,
|
||||
{0xf0, 0xf4, 0xc3, 0xff}: Lime,
|
||||
{0xe6, 0xee, 0x9c, 0xff}: Lime,
|
||||
{0xdc, 0xe7, 0x75, 0xff}: Lime,
|
||||
{0xd4, 0xe1, 0x57, 0xff}: Lime,
|
||||
{0xCD, 0xDC, 0x39, 0xff}: Lime,
|
||||
{0xc0, 0xca, 0x33, 0xff}: Lime,
|
||||
{0xaf, 0xb4, 0x2b, 0xff}: Lime,
|
||||
{0xee, 0xff, 0x41, 0xff}: Lime,
|
||||
{0xc6, 0xff, 0x00, 0xff}: Lime,
|
||||
{0xae, 0xea, 0x00, 0xff}: Lime,
|
||||
{0xff, 0xf9, 0xc4, 0xff}: Yellow,
|
||||
{0xff, 0xf5, 0x9d, 0xff}: Yellow,
|
||||
{0xff, 0xf1, 0x76, 0xff}: Yellow,
|
||||
{0xff, 0xee, 0x58, 0xff}: Yellow,
|
||||
{0xff, 0xff, 0x8d, 0xff}: Yellow,
|
||||
{0xff, 0xff, 0x00, 0xff}: Yellow,
|
||||
{0xff, 0xd5, 0x4f, 0xff}: Yellow,
|
||||
{0xff, 0xca, 0x28, 0xff}: Yellow,
|
||||
{0xe3, 0xce, 0x81, 0xff}: Yellow,
|
||||
{0xd1, 0xaf, 0x52, 0xff}: Yellow,
|
||||
{0xee, 0xbb, 0x2b, 0xff}: Yellow,
|
||||
{0xd3, 0xa8, 0x3a, 0xff}: Yellow,
|
||||
{0xc5, 0xa7, 0x02, 0xff}: Yellow,
|
||||
{0x9f, 0x82, 0x01, 0xff}: Yellow,
|
||||
{0xe8, 0xce, 0x03, 0xff}: Yellow,
|
||||
{0xf9, 0xa8, 0x25, 0xff}: Orange,
|
||||
{0xFF, 0x98, 0x00, 0xff}: Orange,
|
||||
{0xff, 0xa7, 0x26, 0xff}: Orange,
|
||||
{0xfb, 0x8c, 0x00, 0xff}: Orange,
|
||||
{0xf5, 0x7c, 0x00, 0xff}: Orange,
|
||||
{0xef, 0x6c, 0x00, 0xff}: Orange,
|
||||
{0xff, 0x91, 0x00, 0xff}: Orange,
|
||||
{0xff, 0x6d, 0x00, 0xff}: Orange,
|
||||
{0xfd, 0x9a, 0x31, 0xff}: Orange,
|
||||
{0x7d, 0x27, 0x04, 0xff}: Orange,
|
||||
{0xfd, 0x57, 0x1f, 0xff}: Orange,
|
||||
{0xf8, 0x67, 0x04, 0xff}: Orange,
|
||||
{0xfd, 0x9a, 0x00, 0xff}: Orange,
|
||||
{0xfe, 0x8a, 0x00, 0xff}: Orange,
|
||||
{0xf1, 0x96, 0x52, 0xff}: Orange,
|
||||
{0xe5, 0x83, 0x47, 0xff}: Orange,
|
||||
{0xc9, 0x4c, 0x30, 0xff}: Orange,
|
||||
{0x9f, 0x56, 0x01, 0xff}: Orange,
|
||||
{0xfa, 0x68, 0x01, 0xff}: Orange,
|
||||
{0xbb, 0x72, 0x3d, 0xff}: Orange,
|
||||
{0xff, 0x52, 0x52, 0xff}: Red,
|
||||
{0xf4, 0x43, 0x36, 0xff}: Red,
|
||||
{0xef, 0x53, 0x50, 0xff}: Red,
|
||||
{0xe5, 0x39, 0x35, 0xff}: Red,
|
||||
{0xf6, 0x29, 0x2e, 0xff}: Red,
|
||||
{0xfc, 0x25, 0x2d, 0xff}: Red,
|
||||
{0xd3, 0x2f, 0x2f, 0xff}: Red,
|
||||
{0xc6, 0x28, 0x28, 0xff}: Red,
|
||||
{0xba, 0x28, 0x30, 0xff}: Red,
|
||||
{0xb7, 0x1c, 0x1c, 0xff}: Red,
|
||||
{0xd5, 0x00, 0x00, 0xff}: Red,
|
||||
{0xdb, 0x08, 0x06, 0xff}: Red,
|
||||
{0xcf, 0x09, 0x04, 0xff}: Red,
|
||||
{0xd8, 0x1a, 0x14, 0xff}: Red,
|
||||
{0xcc, 0x17, 0x08, 0xff}: Red,
|
||||
{0xd8, 0x0a, 0x07, 0xff}: Red,
|
||||
{0xde, 0x26, 0x16, 0xff}: Red,
|
||||
{0xee, 0x24, 0x0f, 0xff}: Red,
|
||||
{0xa1, 0x21, 0x1f, 0xff}: Red,
|
||||
{0x70, 0x12, 0x19, 0xff}: Red,
|
||||
{0x51, 0x12, 0x18, 0xff}: Red,
|
||||
{0x49, 0x11, 0x14, 0xff}: Red,
|
||||
{0xfc, 0xe4, 0xec, 0xff}: Pink,
|
||||
{0xfd, 0xc8, 0xeb, 0xff}: Pink,
|
||||
{0xe7, 0x9f, 0xa6, 0xff}: Pink,
|
||||
{0xf8, 0xbb, 0xd0, 0xff}: Pink,
|
||||
{0xf4, 0x8f, 0xb1, 0xff}: Pink,
|
||||
{0xff, 0x80, 0xab, 0xff}: Pink,
|
||||
{0xff, 0x40, 0x81, 0xff}: Pink,
|
||||
{0xf5, 0x00, 0x57, 0xff}: Pink,
|
||||
{0xf0, 0x62, 0x92, 0xff}: Pink,
|
||||
{0xec, 0x40, 0x7a, 0xff}: Pink,
|
||||
{0xe9, 0x1e, 0x63, 0xff}: Pink,
|
||||
{0xd8, 0x1b, 0x60, 0xff}: Pink,
|
||||
{0xc2, 0x18, 0x5b, 0xff}: Pink,
|
||||
{0xff, 0x00, 0xff, 0xff}: Magenta,
|
||||
{0xe5, 0x00, 0xe5, 0xff}: Magenta,
|
||||
{0xf0, 0x00, 0xb5, 0xff}: Magenta,
|
||||
{0xce, 0x00, 0x9b, 0xff}: Magenta,
|
||||
{0xc0, 0x05, 0x5b, 0xff}: Magenta,
|
||||
{0xb0, 0x00, 0x85, 0xff}: Magenta,
|
||||
{0xa8, 0x28, 0x63, 0xff}: Magenta,
|
||||
{0x5b, 0x00, 0x2f, 0xff}: Magenta,
|
||||
{0x4b, 0x01, 0x21, 0xff}: Magenta,
|
||||
{0x86, 0x02, 0x25, 0xff}: Magenta,
|
||||
{0xcb, 0x02, 0x3d, 0xff}: Magenta,
|
||||
{0x64, 0x07, 0x1a, 0xff}: Magenta,
|
||||
{0x9e, 0x00, 0x47, 0xff}: Magenta,
|
||||
{0xdc, 0x7a, 0xcf, 0xff}: Magenta,
|
||||
{0xed, 0xde, 0xac, 0xff}: Gold,
|
||||
{0xe8, 0xb4, 0x51, 0xff}: Gold,
|
||||
{0xc0, 0x8a, 0x3e, 0xff}: Gold,
|
||||
{0xa2, 0x7d, 0x4b, 0xff}: Gold,
|
||||
{0x75, 0x55, 0x31, 0xff}: Gold,
|
||||
{0xd1, 0x93, 0x27, 0xff}: Gold,
|
||||
{0xde, 0xa2, 0x53, 0xff}: Gold,
|
||||
{0xd5, 0xaa, 0x6f, 0xff}: Gold,
|
||||
{0xf5, 0xea, 0xd4, 0xff}: Gold,
|
||||
}
|
||||
|
||||
func ColorfulToIndexedColor(actualColor colorful.Color) (result IndexedColor) {
|
||||
var distance = 1.0
|
||||
|
||||
for rgba, i := range IndexedColorMap {
|
||||
colorColorful, _ := colorful.MakeColor(rgba)
|
||||
currentDistance := colorColorful.DistanceLab(actualColor)
|
||||
|
||||
if distance >= currentDistance {
|
||||
distance = currentDistance
|
||||
result = i
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Colors returns color information for a media file.
|
||||
func (m *MediaFile) Colors(thumbPath string) (perception ColorPerception, err error) {
|
||||
func (m *MediaFile) Colors(thumbPath string) (perception colors.ColorPerception, err error) {
|
||||
if !m.IsJpeg() {
|
||||
return perception, errors.New("no color information: not a JPEG file")
|
||||
}
|
||||
|
@ -426,20 +28,20 @@ func (m *MediaFile) Colors(thumbPath string) (perception ColorPerception, err er
|
|||
pixels := float64(width * height)
|
||||
chromaSum := 0.0
|
||||
|
||||
colorCount := make(map[IndexedColor]uint16)
|
||||
colorCount := make(map[colors.Color]uint16)
|
||||
var mainColorCount uint16
|
||||
|
||||
for y := 0; y < height; y++ {
|
||||
for x := 0; x < width; x++ {
|
||||
r, g, b, a := img.At(x, y).RGBA()
|
||||
rgb, _ := colorful.MakeColor(color.RGBA{R: uint8(r), G: uint8(g), B: uint8(b), A: uint8(a)})
|
||||
i := ColorfulToIndexedColor(rgb)
|
||||
i := colors.Colorful(rgb)
|
||||
perception.Colors = append(perception.Colors, i)
|
||||
|
||||
if _, ok := colorCount[i]; ok == true {
|
||||
colorCount[i] += IndexedColorWeight[i]
|
||||
colorCount[i] += colors.Weights[i]
|
||||
} else {
|
||||
colorCount[i] = IndexedColorWeight[i]
|
||||
colorCount[i] = colors.Weights[i]
|
||||
}
|
||||
|
||||
if colorCount[i] > mainColorCount {
|
||||
|
@ -451,11 +53,11 @@ func (m *MediaFile) Colors(thumbPath string) (perception ColorPerception, err er
|
|||
|
||||
chromaSum += c
|
||||
|
||||
perception.Luminance = append(perception.Luminance, Luminance(math.Round(l*15)))
|
||||
perception.Luminance = append(perception.Luminance, colors.Luminance(math.Round(l*15)))
|
||||
}
|
||||
}
|
||||
|
||||
perception.Chroma = Chroma(math.Round((chromaSum / pixels) * 100))
|
||||
perception.Chroma = colors.Chroma(math.Round((chromaSum / pixels) * 100))
|
||||
|
||||
return perception, nil
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/photoprism/photoprism/internal/colors"
|
||||
)
|
||||
|
||||
func TestMediaFile_Colors_Testdata(t *testing.T) {
|
||||
|
@ -20,35 +21,35 @@ func TestMediaFile_Colors_Testdata(t *testing.T) {
|
|||
/*
|
||||
TODO: Add and compare other images in "testdata/"
|
||||
*/
|
||||
expected := map[string]ColorPerception{
|
||||
expected := map[string]colors.ColorPerception{
|
||||
"elephant_mono.jpg": {
|
||||
Colors: IndexedColors{0x2, 0x2, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0},
|
||||
Colors: colors.Colors{0x2, 0x2, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0},
|
||||
MainColor: 0,
|
||||
Luminance: LightMap{0xa, 0x9, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x0},
|
||||
Luminance: colors.LightMap{0xa, 0x9, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x0},
|
||||
Chroma: 0,
|
||||
},
|
||||
"sharks_blue.jpg": {
|
||||
Colors: IndexedColors{0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x4, 0x4, 0x6},
|
||||
Colors: colors.Colors{0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x4, 0x4, 0x6},
|
||||
MainColor: 6,
|
||||
Luminance: LightMap{0x9, 0x7, 0x5, 0x4, 0x3, 0x4, 0x3, 0x3, 0x3},
|
||||
Luminance: colors.LightMap{0x9, 0x7, 0x5, 0x4, 0x3, 0x4, 0x3, 0x3, 0x3},
|
||||
Chroma: 89,
|
||||
},
|
||||
"cat_black.jpg": {
|
||||
Colors: IndexedColors{0x2, 0x1, 0x1, 0x1, 0x2, 0x1, 0x2, 0x5, 0x2},
|
||||
Colors: colors.Colors{0x2, 0x1, 0x1, 0x1, 0x2, 0x1, 0x2, 0x5, 0x2},
|
||||
MainColor: 1,
|
||||
Luminance: LightMap{0x8, 0xc, 0x9, 0x4, 0x2, 0x7, 0xd, 0xd, 0x3},
|
||||
Luminance: colors.LightMap{0x8, 0xc, 0x9, 0x4, 0x2, 0x7, 0xd, 0xd, 0x3},
|
||||
Chroma: 9,
|
||||
},
|
||||
"cat_brown.jpg": {
|
||||
Colors: IndexedColors{0x9, 0x5, 0x1, 0x2, 0x2, 0x1, 0x0, 0x6, 0x2},
|
||||
Colors: colors.Colors{0x9, 0x5, 0x1, 0x2, 0x2, 0x1, 0x0, 0x6, 0x2},
|
||||
MainColor: 5,
|
||||
Luminance: LightMap{0x4, 0x5, 0xb, 0x4, 0x7, 0x3, 0x2, 0x5, 0x7},
|
||||
Luminance: colors.LightMap{0x4, 0x5, 0xb, 0x4, 0x7, 0x3, 0x2, 0x5, 0x7},
|
||||
Chroma: 13,
|
||||
},
|
||||
"cat_yellow_grey.jpg": {
|
||||
Colors: IndexedColors{0x2, 0x1, 0x1, 0x9, 0x0, 0x5, 0xb, 0x0, 0x5},
|
||||
Colors: colors.Colors{0x2, 0x1, 0x1, 0x9, 0x0, 0x5, 0xb, 0x0, 0x5},
|
||||
MainColor: 5,
|
||||
Luminance: LightMap{0x9, 0x5, 0xb, 0x6, 0x1, 0x6, 0xa, 0x1, 0x8},
|
||||
Luminance: colors.LightMap{0x9, 0x5, 0xb, 0x6, 0x1, 0x6, 0xa, 0x1, 0x8},
|
||||
Chroma: 20,
|
||||
},
|
||||
}
|
||||
|
@ -105,10 +106,10 @@ func TestMediaFile_Colors(t *testing.T) {
|
|||
assert.Nil(t, err)
|
||||
assert.Equal(t, 13, p.Chroma.Int())
|
||||
assert.Equal(t, "D", p.Chroma.Hex())
|
||||
assert.IsType(t, IndexedColors{}, p.Colors)
|
||||
assert.IsType(t, colors.Colors{}, p.Colors)
|
||||
assert.Equal(t, "gold", p.MainColor.Name())
|
||||
assert.Equal(t, IndexedColors{0x9, 0x5, 0x1, 0x2, 0x2, 0x1, 0x0, 0x6, 0x2}, p.Colors)
|
||||
assert.Equal(t, LightMap{0x4, 0x5, 0xb, 0x4, 0x7, 0x3, 0x2, 0x5, 0x7}, p.Luminance)
|
||||
assert.Equal(t, colors.Colors{0x9, 0x5, 0x1, 0x2, 0x2, 0x1, 0x0, 0x6, 0x2}, p.Colors)
|
||||
assert.Equal(t, colors.LightMap{0x4, 0x5, 0xb, 0x4, 0x7, 0x3, 0x2, 0x5, 0x7}, p.Luminance)
|
||||
} else {
|
||||
t.Error(err)
|
||||
}
|
||||
|
@ -123,10 +124,10 @@ func TestMediaFile_Colors(t *testing.T) {
|
|||
assert.Nil(t, err)
|
||||
assert.Equal(t, 51, p.Chroma.Int())
|
||||
assert.Equal(t, "33", p.Chroma.Hex())
|
||||
assert.IsType(t, IndexedColors{}, p.Colors)
|
||||
assert.IsType(t, colors.Colors{}, p.Colors)
|
||||
assert.Equal(t, "lime", p.MainColor.Name())
|
||||
assert.Equal(t, IndexedColors{0xa, 0x9, 0xa, 0x9, 0xa, 0xa, 0x9, 0x9, 0x9}, p.Colors)
|
||||
assert.Equal(t, LightMap{0xb, 0x4, 0xa, 0x6, 0x9, 0x8, 0x2, 0x3, 0x4}, p.Luminance)
|
||||
assert.Equal(t, colors.Colors{0xa, 0x9, 0xa, 0x9, 0xa, 0xa, 0x9, 0x9, 0x9}, p.Colors)
|
||||
assert.Equal(t, colors.LightMap{0xb, 0x4, 0xa, 0x6, 0x9, 0x8, 0x2, 0x3, 0x4}, p.Luminance)
|
||||
} else {
|
||||
t.Error(err)
|
||||
}
|
||||
|
@ -141,9 +142,9 @@ func TestMediaFile_Colors(t *testing.T) {
|
|||
assert.Nil(t, err)
|
||||
assert.Equal(t, 7, p.Chroma.Int())
|
||||
assert.Equal(t, "7", p.Chroma.Hex())
|
||||
assert.IsType(t, IndexedColors{}, p.Colors)
|
||||
assert.IsType(t, colors.Colors{}, p.Colors)
|
||||
assert.Equal(t, "blue", p.MainColor.Name())
|
||||
assert.Equal(t, IndexedColors{0x2, 0x6, 0x6, 0x2, 0x2, 0x9, 0x2, 0x0, 0x0}, p.Colors)
|
||||
assert.Equal(t, colors.Colors{0x2, 0x6, 0x6, 0x2, 0x2, 0x9, 0x2, 0x0, 0x0}, p.Colors)
|
||||
} else {
|
||||
t.Error(err)
|
||||
}
|
||||
|
@ -158,10 +159,10 @@ func TestMediaFile_Colors(t *testing.T) {
|
|||
assert.Nil(t, err)
|
||||
assert.Equal(t, 16, p.Chroma.Int())
|
||||
assert.Equal(t, "10", p.Chroma.Hex())
|
||||
assert.IsType(t, IndexedColors{}, p.Colors)
|
||||
assert.IsType(t, colors.Colors{}, p.Colors)
|
||||
assert.Equal(t, "gold", p.MainColor.Name())
|
||||
|
||||
assert.Equal(t, IndexedColors{0x0, 0x0, 0x1, 0x5, 0x5, 0x0, 0x1, 0x5, 0x0}, p.Colors)
|
||||
assert.Equal(t, colors.Colors{0x0, 0x0, 0x1, 0x5, 0x5, 0x0, 0x1, 0x5, 0x0}, p.Colors)
|
||||
} else {
|
||||
t.Error(err)
|
||||
}
|
||||
|
|
|
@ -158,6 +158,9 @@ func (i *Indexer) indexMediaFile(m *MediaFile, o IndexerOptions) IndexResult {
|
|||
}
|
||||
}
|
||||
|
||||
photo.PhotoYear = photo.TakenAt.Year()
|
||||
photo.PhotoMonth = int(photo.TakenAt.Month())
|
||||
|
||||
if photoExists {
|
||||
// Estimate location
|
||||
if o.UpdateLocation && photo.NoLocation() {
|
||||
|
@ -369,21 +372,27 @@ func (i *Indexer) indexLocation(mediaFile *MediaFile, photo *entity.Photo, label
|
|||
return keywords, labels
|
||||
}
|
||||
|
||||
if location.Place.New {
|
||||
event.Publish("count.places", event.Data{
|
||||
"count": 1,
|
||||
})
|
||||
}
|
||||
|
||||
photo.Location = location
|
||||
photo.LocationID = location.ID
|
||||
photo.Place = location.Place
|
||||
photo.PlaceID = location.PlaceID
|
||||
photo.LocationEstimated = false
|
||||
|
||||
photo.Country = entity.NewCountry(location.CountryCode(), location.CountryName()).FirstOrCreate(i.db)
|
||||
country := entity.NewCountry(location.CountryCode(), location.CountryName()).FirstOrCreate(i.db)
|
||||
|
||||
if photo.Country.New {
|
||||
if country.New {
|
||||
event.Publish("count.countries", event.Data{
|
||||
"count": 1,
|
||||
})
|
||||
}
|
||||
|
||||
countryName := photo.Country.CountryName
|
||||
locCategory := location.Category()
|
||||
|
||||
keywords = append(keywords, location.Keywords()...)
|
||||
|
||||
// Append category from reverse location lookup
|
||||
|
@ -395,10 +404,10 @@ func (i *Indexer) indexLocation(mediaFile *MediaFile, photo *entity.Photo, label
|
|||
if (fileChanged || o.UpdateTitle) && photo.PhotoTitleChanged == false {
|
||||
if title := labels.Title(location.Name()); title != "" { // TODO: User defined title format
|
||||
log.Infof("index: using label \"%s\" to create photo title", title)
|
||||
if location.LocCity == "" || len(location.LocCity) > 16 || strings.Contains(title, location.LocCity) {
|
||||
photo.PhotoTitle = fmt.Sprintf("%s / %s / %s", util.Title(title), countryName, photo.TakenAt.Format("2006"))
|
||||
if location.NoCity() || location.LongCity() || location.CityContains(title) {
|
||||
photo.PhotoTitle = fmt.Sprintf("%s / %s / %s", util.Title(title), location.CountryName(), photo.TakenAt.Format("2006"))
|
||||
} else {
|
||||
photo.PhotoTitle = fmt.Sprintf("%s / %s / %s", util.Title(title), location.LocCity, photo.TakenAt.Format("2006"))
|
||||
photo.PhotoTitle = fmt.Sprintf("%s / %s / %s", util.Title(title), location.City(), photo.TakenAt.Format("2006"))
|
||||
}
|
||||
} else if location.Name() != "" && location.City() != "" {
|
||||
if len(location.Name()) > 45 {
|
||||
|
@ -406,13 +415,13 @@ func (i *Indexer) indexLocation(mediaFile *MediaFile, photo *entity.Photo, label
|
|||
} else if len(location.Name()) > 20 || len(location.City()) > 16 || strings.Contains(location.Name(), location.City()) {
|
||||
photo.PhotoTitle = fmt.Sprintf("%s / %s", location.Name(), photo.TakenAt.Format("2006"))
|
||||
} else {
|
||||
photo.PhotoTitle = fmt.Sprintf("%s / %s / %s", location.Name(), location.LocCity, photo.TakenAt.Format("2006"))
|
||||
photo.PhotoTitle = fmt.Sprintf("%s / %s / %s", location.Name(), location.City(), photo.TakenAt.Format("2006"))
|
||||
}
|
||||
} else if location.City() != "" && countryName != "" {
|
||||
} else if location.City() != "" && location.CountryName() != "" {
|
||||
if len(location.City()) > 20 {
|
||||
photo.PhotoTitle = fmt.Sprintf("%s / %s", location.LocCity, photo.TakenAt.Format("2006"))
|
||||
photo.PhotoTitle = fmt.Sprintf("%s / %s", location.City(), photo.TakenAt.Format("2006"))
|
||||
} else {
|
||||
photo.PhotoTitle = fmt.Sprintf("%s / %s / %s", location.LocCity, countryName, photo.TakenAt.Format("2006"))
|
||||
photo.PhotoTitle = fmt.Sprintf("%s / %s / %s", location.City(), location.CountryName(), photo.TakenAt.Format("2006"))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -424,19 +433,24 @@ func (i *Indexer) indexLocation(mediaFile *MediaFile, photo *entity.Photo, label
|
|||
}
|
||||
} else {
|
||||
log.Debugf("index: location cannot be determined precisely (%s)", err.Error())
|
||||
photo.Place = entity.UnknownPlace
|
||||
photo.PlaceID = entity.UnknownPlace.ID
|
||||
}
|
||||
|
||||
photo.PhotoCountry = photo.Place.LocCountry
|
||||
|
||||
return keywords, labels
|
||||
}
|
||||
|
||||
func (i *Indexer) estimateLocation(photo *entity.Photo) {
|
||||
var recentPhoto entity.Photo
|
||||
|
||||
if result := i.db.Unscoped().Order(gorm.Expr("ABS(DATEDIFF(taken_at, ?)) ASC", photo.TakenAt)).Preload("Country").First(&recentPhoto); result.Error == nil {
|
||||
if recentPhoto.Country != nil {
|
||||
photo.Country = recentPhoto.Country
|
||||
if result := i.db.Unscoped().Order(gorm.Expr("ABS(DATEDIFF(taken_at, ?)) ASC", photo.TakenAt)).Preload("Place").First(&recentPhoto); result.Error == nil {
|
||||
if recentPhoto.HasPlace() {
|
||||
photo.Place = recentPhoto.Place
|
||||
photo.PhotoCountry = photo.Place.LocCountry
|
||||
photo.LocationEstimated = true
|
||||
log.Debugf("index: approximate location is \"%s\"", recentPhoto.Country.CountryName)
|
||||
log.Debugf("index: approximate location is \"%s\"", recentPhoto.Place.Label())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,8 +43,8 @@ func TestMediaFile_Location(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, "Himeji", location2.LocCity)
|
||||
assert.Equal(t, "Kinki Region", location2.LocState)
|
||||
assert.Equal(t, "Himeji", location2.City())
|
||||
assert.Equal(t, "Kinki Region", location2.State())
|
||||
})
|
||||
t.Run("cat_brown.jpg", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
|
|
33
internal/repo/category.go
Normal file
33
internal/repo/category.go
Normal file
|
@ -0,0 +1,33 @@
|
|||
package repo
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type CategoryLabel struct {
|
||||
Name string
|
||||
Title string
|
||||
}
|
||||
|
||||
func (s *Repo) CategoryLabels(limit, offset int) (results []CategoryLabel) {
|
||||
q := s.db.NewScope(nil).DB()
|
||||
|
||||
// q.LogMode(true)
|
||||
|
||||
q = q.Table("categories").
|
||||
Select("label_name AS name").
|
||||
Joins("JOIN labels l ON categories.category_id = l.id").
|
||||
Group("label_name").
|
||||
Limit(limit).Offset(offset)
|
||||
|
||||
if err := q.Scan(&results).Error; err != nil {
|
||||
log.Errorf("categories: %s", err.Error())
|
||||
return results
|
||||
}
|
||||
|
||||
for i, l := range results {
|
||||
results[i].Title = strings.Title(l.Name)
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
17
internal/repo/category_test.go
Normal file
17
internal/repo/category_test.go
Normal file
|
@ -0,0 +1,17 @@
|
|||
package repo
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
)
|
||||
|
||||
func TestRepo_CategoryLabels(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
|
||||
search := New(conf.OriginalsPath(), conf.Db())
|
||||
|
||||
categories := search.CategoryLabels(1000, 0)
|
||||
|
||||
t.Logf("categories: %+v", categories)
|
||||
}
|
|
@ -26,6 +26,9 @@ type PhotoResult struct {
|
|||
PhotoName string
|
||||
PhotoTitle string
|
||||
PhotoDescription string
|
||||
PhotoYear int
|
||||
PhotoMonth int
|
||||
PhotoCountry string
|
||||
PhotoArtist string
|
||||
PhotoKeywords string
|
||||
PhotoColors string
|
||||
|
@ -52,18 +55,13 @@ type PhotoResult struct {
|
|||
LensModel string
|
||||
LensMake string
|
||||
|
||||
// Country
|
||||
CountryID string
|
||||
|
||||
// Location
|
||||
LocationID uint64
|
||||
LocName string
|
||||
LocPlace string
|
||||
PlaceID uint64
|
||||
LocLabel string
|
||||
LocCity string
|
||||
LocSuburb string
|
||||
LocState string
|
||||
LocCategory string
|
||||
LocSource string
|
||||
LocCountry string
|
||||
LocationChanged bool
|
||||
LocationEstimated bool
|
||||
|
||||
|
@ -118,12 +116,12 @@ func (s *Repo) Photos(f form.PhotoSearch) (results []PhotoResult, err error) {
|
|||
files.file_orientation, files.file_main_color, files.file_colors, files.file_luminance, files.file_chroma,
|
||||
cameras.camera_make, cameras.camera_model,
|
||||
lenses.lens_make, lenses.lens_model,
|
||||
locations.loc_name, locations.loc_place, locations.loc_city, locations.loc_suburb, locations.loc_state,
|
||||
locations.loc_category, locations.loc_source`).
|
||||
places.loc_label, places.loc_city, places.loc_state, places.loc_country
|
||||
`).
|
||||
Joins("JOIN files ON files.photo_id = photos.id AND files.file_primary AND files.deleted_at IS NULL").
|
||||
Joins("JOIN cameras ON cameras.id = photos.camera_id").
|
||||
Joins("JOIN lenses ON lenses.id = photos.lens_id").
|
||||
Joins("LEFT JOIN locations ON locations.id = photos.location_id").
|
||||
Joins("JOIN places ON photos.place_id = places.id").
|
||||
Joins("LEFT JOIN photos_labels ON photos_labels.photo_id = photos.id").
|
||||
Where("photos.deleted_at IS NULL AND files.file_missing = 0").
|
||||
Group("photos.id, files.id")
|
||||
|
@ -220,7 +218,7 @@ func (s *Repo) Photos(f form.PhotoSearch) (results []PhotoResult, err error) {
|
|||
}
|
||||
|
||||
if f.Country != "" {
|
||||
q = q.Where("photos.country_id = ?", f.Country)
|
||||
q = q.Where("photos.photo_country = ?", f.Country)
|
||||
}
|
||||
|
||||
if f.Title != "" {
|
||||
|
|
17
internal/util/date.go
Normal file
17
internal/util/date.go
Normal file
|
@ -0,0 +1,17 @@
|
|||
package util
|
||||
|
||||
var Months = [...]string{
|
||||
"Unknown",
|
||||
"January",
|
||||
"February",
|
||||
"March",
|
||||
"April",
|
||||
"May",
|
||||
"June",
|
||||
"July",
|
||||
"August",
|
||||
"September",
|
||||
"October",
|
||||
"November",
|
||||
"December",
|
||||
}
|
13
internal/util/date_test.go
Normal file
13
internal/util/date_test.go
Normal file
|
@ -0,0 +1,13 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMonths(t *testing.T) {
|
||||
assert.Equal(t, "Unknown", Months[0])
|
||||
assert.Equal(t, "January", Months[1])
|
||||
assert.Equal(t, "December", Months[12])
|
||||
}
|
Loading…
Reference in a new issue