Some more comment improvements (#257)
* Improve comment in classify package * improve comment in config package * improve entity package comments * grammar error in comments
This commit is contained in:
parent
4fe5aaaccd
commit
55693fab35
30 changed files with 161 additions and 27 deletions
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
This package encapsulates image classification using TensorFlow
|
||||
Package classify encapsulates image classification functionnality using TensorFlow
|
||||
|
||||
Additional information can be found in our Developer Guide:
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ import (
|
|||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// LabelRule defines the rule for a given Label
|
||||
type LabelRule struct {
|
||||
Label string
|
||||
See string
|
||||
|
@ -25,6 +26,7 @@ type LabelRule struct {
|
|||
|
||||
type LabelRules map[string]LabelRule
|
||||
|
||||
// This function generates the rules.go file containing rule extracted from rules.yml file
|
||||
func main() {
|
||||
rules := make(LabelRules)
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package classify
|
||||
|
||||
// LabelRule defines the rule for a given Label
|
||||
type LabelRule struct {
|
||||
Label string
|
||||
Threshold float32
|
||||
|
@ -7,8 +8,10 @@ type LabelRule struct {
|
|||
Priority int
|
||||
}
|
||||
|
||||
// LabelRules is a map of rules with label name as index
|
||||
type LabelRules map[string]LabelRule
|
||||
|
||||
// Find is a getter for LabelRules that give a default rule with a non-zero threshold for missing keys
|
||||
func (rules LabelRules) Find(label string) LabelRule {
|
||||
if rule, ok := rules[label]; ok {
|
||||
return rule
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
// Labels is list of MediaFile labels.
|
||||
type Labels []Label
|
||||
|
||||
// Implements functions for the Sort Interface. Default Labels sort is by priority and uncertainty
|
||||
func (l Labels) Len() int { return len(l) }
|
||||
func (l Labels) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
|
||||
func (l Labels) Less(i, j int) bool {
|
||||
|
@ -19,6 +20,7 @@ func (l Labels) Less(i, j int) bool {
|
|||
}
|
||||
}
|
||||
|
||||
// AppendLabel extends append func by not appending empty label
|
||||
func (l Labels) AppendLabel(label Label) Labels {
|
||||
if label.Name == "" {
|
||||
return l
|
||||
|
@ -27,6 +29,7 @@ func (l Labels) AppendLabel(label Label) Labels {
|
|||
return append(l, label)
|
||||
}
|
||||
|
||||
// Keywords returns all keywords contains in Labels and theire categories
|
||||
func (l Labels) Keywords() (result []string) {
|
||||
for _, label := range l {
|
||||
result = append(result, txt.Keywords(label.Name)...)
|
||||
|
@ -39,9 +42,11 @@ func (l Labels) Keywords() (result []string) {
|
|||
return result
|
||||
}
|
||||
|
||||
// Title gets the best label out a list of labels or fallback to compute a meaningfull default title.
|
||||
func (l Labels) Title(fallback string) string {
|
||||
fallbackRunes := len([]rune(fallback))
|
||||
|
||||
// check if given fallback is valid
|
||||
if fallbackRunes < 2 || fallbackRunes > 25 || txt.ContainsNumber(fallback) {
|
||||
fallback = ""
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ import (
|
|||
tf "github.com/tensorflow/tensorflow/tensorflow/go"
|
||||
)
|
||||
|
||||
// TensorFlow if a wrapper for their low-level API.
|
||||
// TensorFlow is a wrapper for tensorflow low-level API.
|
||||
type TensorFlow struct {
|
||||
conf *config.Config
|
||||
model *tf.SavedModel
|
||||
|
@ -34,6 +34,7 @@ func New(modelsPath string, disabled bool) *TensorFlow {
|
|||
return &TensorFlow{modelsPath: modelsPath, disabled: disabled, modelName: "nasnet", modelTags: []string{"photoprism"}}
|
||||
}
|
||||
|
||||
// Init initialises tensorflow models if not disabled
|
||||
func (t *TensorFlow) Init() (err error) {
|
||||
if t.disabled {
|
||||
return nil
|
||||
|
@ -154,15 +155,17 @@ func (t *TensorFlow) loadModel() error {
|
|||
return t.loadLabels(modelPath)
|
||||
}
|
||||
|
||||
// bestLabels returns the best 5 labels (if enough high probability labels) from the prediction of the model
|
||||
func (t *TensorFlow) bestLabels(probabilities []float32) Labels {
|
||||
// Make a list of label/probability pairs
|
||||
var result Labels
|
||||
|
||||
for i, p := range probabilities {
|
||||
if i >= len(t.labels) {
|
||||
// break if probabilities and labels does not match
|
||||
break
|
||||
}
|
||||
|
||||
// discard labels with low probabilities
|
||||
if p < 0.1 {
|
||||
continue
|
||||
}
|
||||
|
@ -171,10 +174,12 @@ func (t *TensorFlow) bestLabels(probabilities []float32) Labels {
|
|||
|
||||
rule := rules.Find(labelText)
|
||||
|
||||
// discard labels that don't met the threshold
|
||||
if p < rule.Threshold {
|
||||
continue
|
||||
}
|
||||
|
||||
// Get rule label name instead of t.labels name if it exists
|
||||
if rule.Label != "" {
|
||||
labelText = rule.Label
|
||||
}
|
||||
|
@ -189,6 +194,7 @@ func (t *TensorFlow) bestLabels(probabilities []float32) Labels {
|
|||
// Sort by probability
|
||||
sort.Sort(result)
|
||||
|
||||
// return only the 5 best labels
|
||||
if l := len(result); l < 5 {
|
||||
return result[:l]
|
||||
} else {
|
||||
|
@ -196,6 +202,7 @@ func (t *TensorFlow) bestLabels(probabilities []float32) Labels {
|
|||
}
|
||||
}
|
||||
|
||||
// makeTensor converts bytes jpeg image in a tensor object required as tensorflow model input
|
||||
func (t *TensorFlow) makeTensor(image []byte, imageFormat string) (*tf.Tensor, error) {
|
||||
img, err := imaging.Decode(bytes.NewReader(image), imaging.AutoOrientation(true))
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ func isBcrypt(s string) bool {
|
|||
return b
|
||||
}
|
||||
|
||||
// CheckPassword compares given password p with the admin password
|
||||
func (c *Config) CheckPassword(p string) bool {
|
||||
ap := c.AdminPassword()
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
)
|
||||
|
||||
// HTTP client / Web UI config values
|
||||
// ClientConfig contains HTTP client / Web UI config values
|
||||
type ClientConfig map[string]interface{}
|
||||
|
||||
// PublicClientConfig returns reduced config values for non-public sites.
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
|
||||
var log = event.Log
|
||||
|
||||
// Config holds database, cache and all parameters of photoprism
|
||||
type Config struct {
|
||||
db *gorm.DB
|
||||
cache *gc.Cache
|
||||
|
@ -27,6 +28,7 @@ type Config struct {
|
|||
}
|
||||
|
||||
func init() {
|
||||
// initialize the Thumbnails global variable
|
||||
for name, t := range thumb.Types {
|
||||
if t.Public {
|
||||
thumbnail := Thumbnail{Name: name, Width: t.Width, Height: t.Height}
|
||||
|
@ -52,6 +54,7 @@ func initLogger(debug bool) {
|
|||
})
|
||||
}
|
||||
|
||||
// NewConfig initialises a new configuration file
|
||||
func NewConfig(ctx *cli.Context) *Config {
|
||||
initLogger(ctx.GlobalBool("debug"))
|
||||
|
||||
|
@ -107,7 +110,7 @@ func (c *Config) Author() string {
|
|||
return c.config.Author
|
||||
}
|
||||
|
||||
// Description returns the twitter handle for sharing.
|
||||
// Twitter returns the twitter handle for sharing.
|
||||
func (c *Config) Twitter() string {
|
||||
return c.config.Twitter
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"errors"
|
||||
)
|
||||
|
||||
// Define photoprism specific errors
|
||||
var (
|
||||
ErrReadOnly = errors.New("not available in read-only mode")
|
||||
ErrUnauthorized = errors.New("please log in and try again")
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// Global CLI flags
|
||||
// GlobalFlags lists all CLI flags
|
||||
var GlobalFlags = []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "debug",
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// define database drivers const
|
||||
const (
|
||||
DbTiDB = "internal"
|
||||
DbMySQL = "mysql"
|
||||
|
@ -78,7 +79,7 @@ type Params struct {
|
|||
ThumbFilter string `yaml:"thumb-filter" flag:"thumb-filter"`
|
||||
}
|
||||
|
||||
// NewParams() creates a new configuration entity by using two methods:
|
||||
// NewParams creates a new configuration entity by using two methods:
|
||||
//
|
||||
// 1. SetValuesFromFile: This will initialize values from a yaml config file.
|
||||
//
|
||||
|
@ -103,6 +104,7 @@ func NewParams(ctx *cli.Context) *Params {
|
|||
return c
|
||||
}
|
||||
|
||||
// expandFilenames converts path in config to absolute path
|
||||
func (c *Params) expandFilenames() {
|
||||
c.ConfigPath = fs.Abs(c.ConfigPath)
|
||||
c.ResourcesPath = fs.Abs(c.ResourcesPath)
|
||||
|
|
|
@ -9,11 +9,13 @@ import (
|
|||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// Settings contains Web UI settings
|
||||
type Settings struct {
|
||||
Theme string `json:"theme" yaml:"theme" flag:"theme"`
|
||||
Language string `json:"language" yaml:"language" flag:"language"`
|
||||
}
|
||||
|
||||
// NewSettings returns a empty Settings
|
||||
func NewSettings() *Settings {
|
||||
return &Settings{}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import (
|
|||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// define constants used for testing the config package
|
||||
const (
|
||||
TestDataZip = "/tmp/photoprism/testdata.zip"
|
||||
TestDataURL = "https://dl.photoprism.org/fixtures/testdata.zip"
|
||||
|
@ -29,6 +30,7 @@ func testDataPath(assetsPath string) string {
|
|||
return assetsPath + "/testdata"
|
||||
}
|
||||
|
||||
// NewTestParams inits valid params used for testing
|
||||
func NewTestParams() *Params {
|
||||
assetsPath := fs.Abs("../../assets")
|
||||
|
||||
|
@ -52,6 +54,7 @@ func NewTestParams() *Params {
|
|||
return c
|
||||
}
|
||||
|
||||
// NewTestParamsError inits invalid params used for testing
|
||||
func NewTestParamsError() *Params {
|
||||
assetsPath := fs.Abs("../..")
|
||||
|
||||
|
@ -71,6 +74,7 @@ func NewTestParamsError() *Params {
|
|||
return c
|
||||
}
|
||||
|
||||
// TestConfig inits the global testConfig if it was not already initialised
|
||||
func TestConfig() *Config {
|
||||
once.Do(func() {
|
||||
testConfig = NewTestConfig()
|
||||
|
@ -79,6 +83,7 @@ func TestConfig() *Config {
|
|||
return testConfig
|
||||
}
|
||||
|
||||
// NewTestConfig inits valid config used for testing
|
||||
func NewTestConfig() *Config {
|
||||
log.SetLevel(logrus.DebugLevel)
|
||||
|
||||
|
@ -102,6 +107,7 @@ func NewTestConfig() *Config {
|
|||
return c
|
||||
}
|
||||
|
||||
// NewTestErrorConfig inits invalid config used for testing
|
||||
func NewTestErrorConfig() *Config {
|
||||
log.SetLevel(logrus.DebugLevel)
|
||||
|
||||
|
@ -115,7 +121,7 @@ func NewTestErrorConfig() *Config {
|
|||
return c
|
||||
}
|
||||
|
||||
// Returns example cli config for testing
|
||||
// CliTestContext returns example cli config for testing
|
||||
func CliTestContext() *cli.Context {
|
||||
config := NewTestParams()
|
||||
|
||||
|
@ -146,6 +152,7 @@ func CliTestContext() *cli.Context {
|
|||
return c
|
||||
}
|
||||
|
||||
// RemoveTestData deletes files in import, export, originals and cache folders
|
||||
func (c *Config) RemoveTestData(t *testing.T) {
|
||||
os.RemoveAll(c.ImportPath())
|
||||
os.RemoveAll(c.ExportPath())
|
||||
|
@ -153,6 +160,7 @@ func (c *Config) RemoveTestData(t *testing.T) {
|
|||
os.RemoveAll(c.CachePath())
|
||||
}
|
||||
|
||||
// DownloadTestData downloads test data from photoprism.org server
|
||||
func (c *Config) DownloadTestData(t *testing.T) {
|
||||
if fs.FileExists(TestDataZip) {
|
||||
hash := fs.Hash(TestDataZip)
|
||||
|
@ -172,12 +180,14 @@ func (c *Config) DownloadTestData(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// UnzipTestData in default test folder
|
||||
func (c *Config) UnzipTestData(t *testing.T) {
|
||||
if _, err := fs.Unzip(TestDataZip, testDataPath(c.AssetsPath())); err != nil {
|
||||
t.Logf("could not unzip test data: %s\n", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// InitializeTestData using testing constant
|
||||
func (c *Config) InitializeTestData(t *testing.T) {
|
||||
t.Log("initializing test data")
|
||||
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
package config
|
||||
|
||||
// Thumbnail gives direct access to width and height for a thumbnail setting
|
||||
type Thumbnail struct {
|
||||
Name string
|
||||
Width int
|
||||
Height int
|
||||
}
|
||||
|
||||
// Thumbnails is a list of default thumbnail size available for the app
|
||||
var Thumbnails []Thumbnail
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
)
|
||||
|
||||
// Photo album
|
||||
// Album represents a photo album
|
||||
type Album struct {
|
||||
ID uint `gorm:"primary_key"`
|
||||
CoverUUID string `gorm:"type:varbinary(36);"`
|
||||
|
@ -29,6 +29,7 @@ type Album struct {
|
|||
DeletedAt *time.Time `sql:"index"`
|
||||
}
|
||||
|
||||
// BeforeCreate computes a random UUID when a new album is created in database
|
||||
func (m *Album) BeforeCreate(scope *gorm.Scope) error {
|
||||
if err := scope.SetColumn("AlbumUUID", rnd.PPID('a')); err != nil {
|
||||
return err
|
||||
|
@ -37,6 +38,7 @@ func (m *Album) BeforeCreate(scope *gorm.Scope) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// NewAlbum creates a new album; default name is current month and year
|
||||
func NewAlbum(albumName string) *Album {
|
||||
albumName = strings.TrimSpace(albumName)
|
||||
|
||||
|
@ -54,6 +56,7 @@ func NewAlbum(albumName string) *Album {
|
|||
return result
|
||||
}
|
||||
|
||||
// Rename an existing album
|
||||
func (m *Album) Rename(albumName string) {
|
||||
if albumName == "" {
|
||||
albumName = m.CreatedAt.Format("January 2006")
|
||||
|
|
|
@ -24,6 +24,7 @@ type Camera struct {
|
|||
DeletedAt *time.Time `sql:"index"`
|
||||
}
|
||||
|
||||
// NewCamera creates a camera entity from a model name and a make name.
|
||||
func NewCamera(modelName string, makeName string) *Camera {
|
||||
makeName = strings.TrimSpace(makeName)
|
||||
|
||||
|
@ -50,6 +51,7 @@ func NewCamera(modelName string, makeName string) *Camera {
|
|||
return result
|
||||
}
|
||||
|
||||
// FirstOrCreate checks wether the camera model exist already in the database
|
||||
func (m *Camera) FirstOrCreate(db *gorm.DB) *Camera {
|
||||
mutex.Db.Lock()
|
||||
defer mutex.Db.Unlock()
|
||||
|
@ -61,6 +63,7 @@ func (m *Camera) FirstOrCreate(db *gorm.DB) *Camera {
|
|||
return m
|
||||
}
|
||||
|
||||
// String returns a string designing the given Camera entity
|
||||
func (m *Camera) String() string {
|
||||
if m.CameraMake != "" && m.CameraModel != "" {
|
||||
return fmt.Sprintf("%s %s", m.CameraMake, m.CameraModel)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package entity
|
||||
|
||||
// Labels can have zero or more categories with the same or a similar meaning
|
||||
// Category of labels regroups labels with the same or a similar meaning using a main/root label
|
||||
type Category struct {
|
||||
LabelID uint `gorm:"primary_key;auto_increment:false"`
|
||||
CategoryID uint `gorm:"primary_key;auto_increment:false"`
|
||||
|
@ -8,6 +8,7 @@ type Category struct {
|
|||
Category *Label
|
||||
}
|
||||
|
||||
// TableName returns Category table identifier "categories"
|
||||
func (Category) TableName() string {
|
||||
return "categories"
|
||||
}
|
||||
|
|
|
@ -7,12 +7,14 @@ import (
|
|||
"github.com/photoprism/photoprism/internal/mutex"
|
||||
)
|
||||
|
||||
// altCountryNames defines mapping between different names for the same countriy
|
||||
var altCountryNames = map[string]string{
|
||||
"United States of America": "USA",
|
||||
"United States": "USA",
|
||||
"": "Unknown",
|
||||
}
|
||||
|
||||
// Country represents a country location, used for labeling photos.
|
||||
type Country struct {
|
||||
ID string `gorm:"primary_key"`
|
||||
CountrySlug string `gorm:"type:varbinary(128);unique_index;"`
|
||||
|
@ -24,13 +26,15 @@ type Country struct {
|
|||
New bool `gorm:"-"`
|
||||
}
|
||||
|
||||
// UnknownCountry is the default country
|
||||
var UnknownCountry = NewCountry("zz", maps.CountryNames["zz"])
|
||||
|
||||
// CreateUnknownCountry is used to initialize the database with the default country
|
||||
func CreateUnknownCountry(db *gorm.DB) {
|
||||
UnknownCountry.FirstOrCreate(db)
|
||||
}
|
||||
|
||||
// Create a new country
|
||||
// NewCountry creates a new country, with default country code if not provided
|
||||
func NewCountry(countryCode string, countryName string) *Country {
|
||||
if countryCode == "" {
|
||||
countryCode = "zz"
|
||||
|
@ -51,6 +55,7 @@ func NewCountry(countryCode string, countryName string) *Country {
|
|||
return result
|
||||
}
|
||||
|
||||
// FirstOrCreate checks wether the country exist already in the database (using countryCode)
|
||||
func (m *Country) FirstOrCreate(db *gorm.DB) *Country {
|
||||
mutex.Db.Lock()
|
||||
defer mutex.Db.Unlock()
|
||||
|
@ -62,14 +67,17 @@ func (m *Country) FirstOrCreate(db *gorm.DB) *Country {
|
|||
return m
|
||||
}
|
||||
|
||||
// AfterCreate sets the New column used for database callback
|
||||
func (m *Country) AfterCreate(scope *gorm.Scope) error {
|
||||
return scope.SetColumn("New", true)
|
||||
}
|
||||
|
||||
// Code returns country code
|
||||
func (m *Country) Code() string {
|
||||
return m.ID
|
||||
}
|
||||
|
||||
// Name returns country name
|
||||
func (m *Country) Name() string {
|
||||
return m.CountryName
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
This package contains models for data storage based on GORM.
|
||||
Package entity contains models for data storage based on GORM.
|
||||
|
||||
See http://gorm.io/docs/ for more information about GORM.
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
)
|
||||
|
||||
// Events
|
||||
// Event defines temporal event that can be used to link photos together
|
||||
type Event struct {
|
||||
EventUUID string `gorm:"type:varbinary(36);unique_index;"`
|
||||
EventSlug string `gorm:"type:varbinary(128);unique_index;"`
|
||||
|
@ -25,10 +25,12 @@ type Event struct {
|
|||
DeletedAt *time.Time `sql:"index"`
|
||||
}
|
||||
|
||||
// TableName returns Event table identifier "events"
|
||||
func (Event) TableName() string {
|
||||
return "events"
|
||||
}
|
||||
|
||||
// BeforeCreate computes a random UUID when a new event is created in database
|
||||
func (e *Event) BeforeCreate(scope *gorm.Scope) error {
|
||||
return scope.SetColumn("EventUUID", rnd.PPID('e'))
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
)
|
||||
|
||||
// An image or sidecar file that belongs to a photo
|
||||
// File represents an image or sidecar file that belongs to a photo
|
||||
type File struct {
|
||||
ID uint `gorm:"primary_key"`
|
||||
Photo *Photo
|
||||
|
@ -47,6 +47,7 @@ type File struct {
|
|||
DeletedAt *time.Time `sql:"index"`
|
||||
}
|
||||
|
||||
// FirstFileByHash gets a file in db from its hash
|
||||
func FirstFileByHash(db *gorm.DB, fileHash string) (File, error) {
|
||||
var file File
|
||||
|
||||
|
@ -55,10 +56,12 @@ func FirstFileByHash(db *gorm.DB, fileHash string) (File, error) {
|
|||
return file, q.Error
|
||||
}
|
||||
|
||||
// BeforeCreate computes a random UUID when a new file is created in database
|
||||
func (m *File) BeforeCreate(scope *gorm.Scope) error {
|
||||
return scope.SetColumn("FileUUID", rnd.PPID('f'))
|
||||
}
|
||||
|
||||
// DownloadFileName returns a name useful for download links
|
||||
func (m *File) DownloadFileName() string {
|
||||
if m.Photo == nil {
|
||||
return fmt.Sprintf("%s.%s", m.FileHash, m.FileType)
|
||||
|
@ -79,6 +82,7 @@ func (m *File) DownloadFileName() string {
|
|||
return result
|
||||
}
|
||||
|
||||
// Changed checks wether given filesize or modified time are matching the current File
|
||||
func (m File) Changed(fileSize int64, fileModified time.Time) bool {
|
||||
if m.FileSize != fileSize {
|
||||
return true
|
||||
|
|
|
@ -7,13 +7,14 @@ import (
|
|||
"github.com/photoprism/photoprism/internal/mutex"
|
||||
)
|
||||
|
||||
// Keyword for full text search
|
||||
// Keyword used for full text search
|
||||
type Keyword struct {
|
||||
ID uint `gorm:"primary_key"`
|
||||
Keyword string `gorm:"type:varchar(64);index;"`
|
||||
Skip bool
|
||||
}
|
||||
|
||||
// NewKeyword registers a new keyword in database
|
||||
func NewKeyword(keyword string) *Keyword {
|
||||
keyword = strings.ToLower(strings.TrimSpace(keyword))
|
||||
|
||||
|
@ -24,6 +25,7 @@ func NewKeyword(keyword string) *Keyword {
|
|||
return result
|
||||
}
|
||||
|
||||
// FirstOrCreate checks wether the keyword already exist in the database
|
||||
func (m *Keyword) FirstOrCreate(db *gorm.DB) *Keyword {
|
||||
mutex.Db.Lock()
|
||||
defer mutex.Db.Unlock()
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
)
|
||||
|
||||
// Labels for photo, album and location categorization
|
||||
// Label is used for photo, album and location categorization
|
||||
type Label struct {
|
||||
ID uint `gorm:"primary_key"`
|
||||
LabelUUID string `gorm:"type:varbinary(36);unique_index;"`
|
||||
|
@ -28,6 +28,7 @@ type Label struct {
|
|||
New bool `gorm:"-"`
|
||||
}
|
||||
|
||||
// BeforeCreate computes a random UUID when a new label is created in database
|
||||
func (m *Label) BeforeCreate(scope *gorm.Scope) error {
|
||||
if err := scope.SetColumn("LabelUUID", rnd.PPID('l')); err != nil {
|
||||
log.Errorf("label: %s", err)
|
||||
|
@ -37,6 +38,7 @@ func (m *Label) BeforeCreate(scope *gorm.Scope) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// NewLabel creates a label in database with a given name and priority
|
||||
func NewLabel(labelName string, labelPriority int) *Label {
|
||||
labelName = strings.TrimSpace(labelName)
|
||||
|
||||
|
@ -55,6 +57,7 @@ func NewLabel(labelName string, labelPriority int) *Label {
|
|||
return result
|
||||
}
|
||||
|
||||
// FirstOrCreate checks wether the label already exists in the database
|
||||
func (m *Label) FirstOrCreate(db *gorm.DB) *Label {
|
||||
mutex.Db.Lock()
|
||||
defer mutex.Db.Unlock()
|
||||
|
@ -66,10 +69,12 @@ func (m *Label) FirstOrCreate(db *gorm.DB) *Label {
|
|||
return m
|
||||
}
|
||||
|
||||
// AfterCreate sets the New column used for database callback
|
||||
func (m *Label) AfterCreate(scope *gorm.Scope) error {
|
||||
return scope.SetColumn("New", true)
|
||||
}
|
||||
|
||||
// Rename an existing label
|
||||
func (m *Label) Rename(name string) {
|
||||
name = txt.Clip(name, 128)
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
"github.com/photoprism/photoprism/internal/mutex"
|
||||
)
|
||||
|
||||
// Camera lens (as extracted from UpdateExif metadata)
|
||||
// Lens represents camera lens (as extracted from UpdateExif metadata)
|
||||
type Lens struct {
|
||||
ID uint `gorm:"primary_key"`
|
||||
LensSlug string `gorm:"type:varbinary(128);unique_index;"`
|
||||
|
@ -24,10 +24,12 @@ type Lens struct {
|
|||
DeletedAt *time.Time `sql:"index"`
|
||||
}
|
||||
|
||||
// TableName returns Lens table identifier "lens"
|
||||
func (Lens) TableName() string {
|
||||
return "lenses"
|
||||
}
|
||||
|
||||
// NewLens creates a new lens in database
|
||||
func NewLens(modelName string, makeName string) *Lens {
|
||||
modelName = strings.TrimSpace(modelName)
|
||||
makeName = strings.TrimSpace(makeName)
|
||||
|
@ -47,6 +49,7 @@ func NewLens(modelName string, makeName string) *Lens {
|
|||
return result
|
||||
}
|
||||
|
||||
// FirstOrCreate checks wether the lens already exists in the database
|
||||
func (m *Lens) FirstOrCreate(db *gorm.DB) *Lens {
|
||||
mutex.Db.Lock()
|
||||
defer mutex.Db.Unlock()
|
||||
|
|
|
@ -14,7 +14,7 @@ import (
|
|||
|
||||
var locationMutex = sync.Mutex{}
|
||||
|
||||
// Photo location
|
||||
// Location used to associate photos to location
|
||||
type Location struct {
|
||||
ID string `gorm:"type:varbinary(16);primary_key;auto_increment:false;"`
|
||||
PlaceID string `gorm:"type:varbinary(16);"`
|
||||
|
@ -26,16 +26,17 @@ type Location struct {
|
|||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
// Locks locations for updates
|
||||
// Lock location for updates
|
||||
func (Location) Lock() {
|
||||
locationMutex.Lock()
|
||||
}
|
||||
|
||||
// Unlock locations for updates
|
||||
// Unlock location for updates
|
||||
func (Location) Unlock() {
|
||||
locationMutex.Unlock()
|
||||
}
|
||||
|
||||
// NewLocation creates a location using a token extracted from coordinate
|
||||
func NewLocation(lat, lng float64) *Location {
|
||||
result := &Location{}
|
||||
|
||||
|
@ -44,6 +45,7 @@ func NewLocation(lat, lng float64) *Location {
|
|||
return result
|
||||
}
|
||||
|
||||
// Find gets the location using either the db or the api if not in the db
|
||||
func (m *Location) Find(db *gorm.DB, api string) error {
|
||||
mutex.Db.Lock()
|
||||
defer mutex.Db.Unlock()
|
||||
|
@ -83,6 +85,7 @@ func (m *Location) Find(db *gorm.DB, api string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Keywords computes keyword based on a Location
|
||||
func (m *Location) Keywords() []string {
|
||||
result := []string{
|
||||
strings.ToLower(m.City()),
|
||||
|
@ -98,66 +101,82 @@ func (m *Location) Keywords() []string {
|
|||
return result
|
||||
}
|
||||
|
||||
// Unknown checks if the location has no id
|
||||
func (m *Location) Unknown() bool {
|
||||
return m.ID == ""
|
||||
}
|
||||
|
||||
// Name returns name of location
|
||||
func (m *Location) Name() string {
|
||||
return m.LocName
|
||||
}
|
||||
|
||||
// NoName checks if the location has no name
|
||||
func (m *Location) NoName() bool {
|
||||
return m.LocName == ""
|
||||
}
|
||||
|
||||
// Category returns the location category
|
||||
func (m *Location) Category() string {
|
||||
return m.LocCategory
|
||||
}
|
||||
|
||||
// NoCategory checks id the location has no category
|
||||
func (m *Location) NoCategory() bool {
|
||||
return m.LocCategory == ""
|
||||
}
|
||||
|
||||
// Label returns the location place label
|
||||
func (m *Location) Label() string {
|
||||
return m.Place.Label()
|
||||
}
|
||||
|
||||
// City returns the location place city
|
||||
func (m *Location) City() string {
|
||||
return m.Place.City()
|
||||
}
|
||||
|
||||
// LongCity checks if the city name is more than 16 char
|
||||
func (m *Location) LongCity() bool {
|
||||
return len(m.City()) > 16
|
||||
}
|
||||
|
||||
// NoCity checks if the location has no city
|
||||
func (m *Location) NoCity() bool {
|
||||
return m.City() == ""
|
||||
}
|
||||
|
||||
// CityContains checks if the location city contains the text string
|
||||
func (m *Location) CityContains(text string) bool {
|
||||
return strings.Contains(text, m.City())
|
||||
}
|
||||
|
||||
// State returns the location place state
|
||||
func (m *Location) State() string {
|
||||
return m.Place.State()
|
||||
}
|
||||
|
||||
// NoState checks if the location place has no state
|
||||
func (m *Location) NoState() bool {
|
||||
return m.Place.State() == ""
|
||||
}
|
||||
|
||||
// CountryCode returns the location place country code
|
||||
func (m *Location) CountryCode() string {
|
||||
return m.Place.CountryCode()
|
||||
}
|
||||
|
||||
// CountryName returns the location place country name
|
||||
func (m *Location) CountryName() string {
|
||||
return m.Place.CountryName()
|
||||
}
|
||||
|
||||
// Notes returns the locations place notes
|
||||
func (m *Location) Notes() string {
|
||||
return m.Place.Notes()
|
||||
}
|
||||
|
||||
// Source returns the source of location information
|
||||
func (m *Location) Source() string {
|
||||
return m.LocSource
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
"github.com/ulule/deepcopier"
|
||||
)
|
||||
|
||||
// Photo represents a photo that can have multiple image or sidecar files.
|
||||
// Photo represents a photo, all its properties, and link to all its images and sidecar files.
|
||||
type Photo struct {
|
||||
ID uint `gorm:"primary_key"`
|
||||
TakenAt time.Time `gorm:"type:datetime;index:idx_photos_taken_uuid;" json:"TakenAt"`
|
||||
|
@ -76,6 +76,7 @@ func SavePhoto(model Photo, form form.Photo, db *gorm.DB) error {
|
|||
return db.Save(&model).Error
|
||||
}
|
||||
|
||||
// BeforeCreate computes a unique UUID, and set a default takenAt before indexing a new photo
|
||||
func (m *Photo) BeforeCreate(scope *gorm.Scope) error {
|
||||
if err := scope.SetColumn("PhotoUUID", rnd.PPID('p')); err != nil {
|
||||
return err
|
||||
|
@ -96,6 +97,7 @@ func (m *Photo) BeforeCreate(scope *gorm.Scope) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// BeforeSave ensures the existence of TakenAt properties before indexing or updating a photo
|
||||
func (m *Photo) BeforeSave(scope *gorm.Scope) error {
|
||||
if m.TakenAt.IsZero() || m.TakenAtLocal.IsZero() {
|
||||
now := time.Now()
|
||||
|
@ -112,6 +114,7 @@ func (m *Photo) BeforeSave(scope *gorm.Scope) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// IndexKeywords adds given keywords to the photo entry
|
||||
func (m *Photo) IndexKeywords(keywords []string, db *gorm.DB) {
|
||||
var keywordIds []uint
|
||||
|
||||
|
@ -145,6 +148,7 @@ func (m *Photo) IndexKeywords(keywords []string, db *gorm.DB) {
|
|||
db.Where("photo_id = ? AND keyword_id NOT IN (?)", m.ID, keywordIds).Delete(&PhotoKeyword{})
|
||||
}
|
||||
|
||||
// PreloadFiles prepares gorm scope to retrieve photo file
|
||||
func (m *Photo) PreloadFiles(db *gorm.DB) {
|
||||
q := db.NewScope(nil).DB().
|
||||
Table("files").
|
||||
|
@ -166,6 +170,7 @@ func (m *Photo) PreloadFiles(db *gorm.DB) {
|
|||
logError(q.Scan(&m.Labels))
|
||||
} */
|
||||
|
||||
// PreloadKeywords prepares gorm scope to retrieve photo keywords
|
||||
func (m *Photo) PreloadKeywords(db *gorm.DB) {
|
||||
q := db.NewScope(nil).DB().
|
||||
Table("keywords").
|
||||
|
@ -176,6 +181,7 @@ func (m *Photo) PreloadKeywords(db *gorm.DB) {
|
|||
logError(q.Scan(&m.Keywords))
|
||||
}
|
||||
|
||||
// PreloadAlbums prepares gorm scope to retrieve photo albums
|
||||
func (m *Photo) PreloadAlbums(db *gorm.DB) {
|
||||
q := db.NewScope(nil).DB().
|
||||
Table("albums").
|
||||
|
@ -187,6 +193,7 @@ func (m *Photo) PreloadAlbums(db *gorm.DB) {
|
|||
logError(q.Scan(&m.Albums))
|
||||
}
|
||||
|
||||
// PreloadMany prepares gorm scope to retrieve photo file, albums and keywords
|
||||
func (m *Photo) PreloadMany(db *gorm.DB) {
|
||||
m.PreloadFiles(db)
|
||||
// m.PreloadLabels(db)
|
||||
|
@ -194,54 +201,67 @@ func (m *Photo) PreloadMany(db *gorm.DB) {
|
|||
m.PreloadAlbums(db)
|
||||
}
|
||||
|
||||
// NoLocation checks if the photo has no location
|
||||
func (m *Photo) NoLocation() bool {
|
||||
return m.LocationID == ""
|
||||
}
|
||||
|
||||
// HasLocation checks if the photo has a location
|
||||
func (m *Photo) HasLocation() bool {
|
||||
return m.LocationID != ""
|
||||
}
|
||||
|
||||
// NoPlace checks if the photo has no Place
|
||||
func (m *Photo) NoPlace() bool {
|
||||
return len(m.PlaceID) < 2
|
||||
}
|
||||
|
||||
// HasPlace checks if the photo has a Place
|
||||
func (m *Photo) HasPlace() bool {
|
||||
return len(m.PlaceID) >= 2
|
||||
}
|
||||
|
||||
// NoTitle checks if the photo has no Title
|
||||
func (m *Photo) NoTitle() bool {
|
||||
return m.PhotoTitle == ""
|
||||
}
|
||||
|
||||
// NoDescription checks if the photo has no Description
|
||||
func (m *Photo) NoDescription() bool {
|
||||
return m.PhotoDescription == ""
|
||||
}
|
||||
|
||||
// NoNotes checks if the photo has no Notes
|
||||
func (m *Photo) NoNotes() bool {
|
||||
return m.PhotoNotes == ""
|
||||
}
|
||||
|
||||
// NoArtist checks if the photo has no Artist
|
||||
func (m *Photo) NoArtist() bool {
|
||||
return m.PhotoArtist == ""
|
||||
}
|
||||
|
||||
// NoCopyright checks if the photo has no Copyright
|
||||
func (m *Photo) NoCopyright() bool {
|
||||
return m.PhotoCopyright == ""
|
||||
}
|
||||
|
||||
// NoSubject checks if the photo has no Subject
|
||||
func (m *Photo) NoSubject() bool {
|
||||
return m.PhotoSubject == ""
|
||||
}
|
||||
|
||||
// NoKeywords checks if the photo has no Keywords
|
||||
func (m *Photo) NoKeywords() bool {
|
||||
return m.PhotoKeywords == ""
|
||||
}
|
||||
|
||||
// NoCameraSerial checks if the photo has no CameraSerial
|
||||
func (m *Photo) NoCameraSerial() bool {
|
||||
return m.CameraSerial == ""
|
||||
}
|
||||
|
||||
// HasTitle checks if the photo has a Title
|
||||
func (m *Photo) HasTitle() bool {
|
||||
return m.PhotoTitle != ""
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
"github.com/photoprism/photoprism/internal/mutex"
|
||||
)
|
||||
|
||||
// Photos can be added to multiple albums
|
||||
// PhotoAlbum represents the many_to_many relation between Photo and Album
|
||||
type PhotoAlbum struct {
|
||||
PhotoUUID string `gorm:"type:varbinary(36);primary_key;auto_increment:false"`
|
||||
AlbumUUID string `gorm:"type:varbinary(36);primary_key;auto_increment:false;index"`
|
||||
|
@ -18,10 +18,12 @@ type PhotoAlbum struct {
|
|||
Album *Album
|
||||
}
|
||||
|
||||
// TableName returns PhotoAlbum table identifier "photos_albums"
|
||||
func (PhotoAlbum) TableName() string {
|
||||
return "photos_albums"
|
||||
}
|
||||
|
||||
// NewPhotoAlbum registers an photo and album association using UUID
|
||||
func NewPhotoAlbum(photoUUID, albumUUID string) *PhotoAlbum {
|
||||
result := &PhotoAlbum{
|
||||
PhotoUUID: photoUUID,
|
||||
|
@ -31,6 +33,7 @@ func NewPhotoAlbum(photoUUID, albumUUID string) *PhotoAlbum {
|
|||
return result
|
||||
}
|
||||
|
||||
// FirstOrCreate checks wether the PhotoAlbum relation already exist in the database before the creation
|
||||
func (m *PhotoAlbum) FirstOrCreate(db *gorm.DB) *PhotoAlbum {
|
||||
mutex.Db.Lock()
|
||||
defer mutex.Db.Unlock()
|
||||
|
|
|
@ -5,15 +5,18 @@ import (
|
|||
"github.com/photoprism/photoprism/internal/mutex"
|
||||
)
|
||||
|
||||
// PhotoKeyword represents the many-to-many relation between Photo and Keyword
|
||||
type PhotoKeyword struct {
|
||||
PhotoID uint `gorm:"primary_key;auto_increment:false"`
|
||||
KeywordID uint `gorm:"primary_key;auto_increment:false;index"`
|
||||
}
|
||||
|
||||
// TableName returns PhotoKeyword table identifier "photos_keywords"
|
||||
func (PhotoKeyword) TableName() string {
|
||||
return "photos_keywords"
|
||||
}
|
||||
|
||||
// NewPhotoKeyword registers a new PhotoKeyword relation
|
||||
func NewPhotoKeyword(photoID, keywordID uint) *PhotoKeyword {
|
||||
result := &PhotoKeyword{
|
||||
PhotoID: photoID,
|
||||
|
@ -23,6 +26,7 @@ func NewPhotoKeyword(photoID, keywordID uint) *PhotoKeyword {
|
|||
return result
|
||||
}
|
||||
|
||||
// FirstOrCreate checks wether the PhotoKeywords relation already exist in the database before the creation
|
||||
func (m *PhotoKeyword) FirstOrCreate(db *gorm.DB) *PhotoKeyword {
|
||||
mutex.Db.Lock()
|
||||
defer mutex.Db.Unlock()
|
||||
|
|
|
@ -5,7 +5,8 @@ import (
|
|||
"github.com/photoprism/photoprism/internal/mutex"
|
||||
)
|
||||
|
||||
// Photo labels are weighted by uncertainty (100 - confidence)
|
||||
// PhotoLabel represents the many-to-many relation between Photo and label.
|
||||
// Labels are weighted by uncertainty (100 - confidence)
|
||||
type PhotoLabel struct {
|
||||
PhotoID uint `gorm:"primary_key;auto_increment:false"`
|
||||
LabelID uint `gorm:"primary_key;auto_increment:false;index"`
|
||||
|
@ -15,14 +16,16 @@ type PhotoLabel struct {
|
|||
Label *Label
|
||||
}
|
||||
|
||||
// TableName returns PhotoLabel table identifier "photos_labels"
|
||||
func (PhotoLabel) TableName() string {
|
||||
return "photos_labels"
|
||||
}
|
||||
|
||||
func NewPhotoLabel(photoId, labelId uint, uncertainty int, source string) *PhotoLabel {
|
||||
// NewPhotoLabel registers a new PhotoLabel relation with an uncertainty and a source of label
|
||||
func NewPhotoLabel(photoID, labelID uint, uncertainty int, source string) *PhotoLabel {
|
||||
result := &PhotoLabel{
|
||||
PhotoID: photoId,
|
||||
LabelID: labelId,
|
||||
PhotoID: photoID,
|
||||
LabelID: labelID,
|
||||
LabelUncertainty: uncertainty,
|
||||
LabelSource: source,
|
||||
}
|
||||
|
@ -30,6 +33,7 @@ func NewPhotoLabel(photoId, labelId uint, uncertainty int, source string) *Photo
|
|||
return result
|
||||
}
|
||||
|
||||
// FirstOrCreate checks wether the PhotoLabel relation already exist in the database before the creation
|
||||
func (m *PhotoLabel) FirstOrCreate(db *gorm.DB) *PhotoLabel {
|
||||
mutex.Db.Lock()
|
||||
defer mutex.Db.Unlock()
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
"github.com/photoprism/photoprism/internal/mutex"
|
||||
)
|
||||
|
||||
// Photo place
|
||||
// Place used to associate photos to places
|
||||
type Place struct {
|
||||
ID string `gorm:"type:varbinary(16);primary_key;auto_increment:false;"`
|
||||
LocLabel string `gorm:"type:varbinary(512);unique_index;"`
|
||||
|
@ -22,16 +22,20 @@ type Place struct {
|
|||
New bool `gorm:"-"`
|
||||
}
|
||||
|
||||
// UnknownPlace is the default unknown place
|
||||
var UnknownPlace = NewPlace("-", "Unknown", "Unknown", "Unknown", "zz")
|
||||
|
||||
// CreateUnknownPlace initializes default place in the database
|
||||
func CreateUnknownPlace(db *gorm.DB) {
|
||||
UnknownPlace.FirstOrCreate(db)
|
||||
}
|
||||
|
||||
// AfterCreate sets the New column used for database callback
|
||||
func (m *Place) AfterCreate(scope *gorm.Scope) error {
|
||||
return scope.SetColumn("New", true)
|
||||
}
|
||||
|
||||
// FindPlace returns place from a token
|
||||
func FindPlace(token string, db *gorm.DB) *Place {
|
||||
place := &Place{}
|
||||
|
||||
|
@ -42,6 +46,7 @@ func FindPlace(token string, db *gorm.DB) *Place {
|
|||
return place
|
||||
}
|
||||
|
||||
// FindPlaceByLabel returns a place from an id or a label
|
||||
func FindPlaceByLabel(id string, label string, db *gorm.DB) *Place {
|
||||
place := &Place{}
|
||||
|
||||
|
@ -52,6 +57,7 @@ func FindPlaceByLabel(id string, label string, db *gorm.DB) *Place {
|
|||
return place
|
||||
}
|
||||
|
||||
// NewPlace registers a new place in database
|
||||
func NewPlace(id, label, city, state, countryCode string) *Place {
|
||||
result := &Place{
|
||||
ID: id,
|
||||
|
@ -64,6 +70,7 @@ func NewPlace(id, label, city, state, countryCode string) *Place {
|
|||
return result
|
||||
}
|
||||
|
||||
// Find returns db record of place
|
||||
func (m *Place) Find(db *gorm.DB) error {
|
||||
if err := db.First(m, "id = ?", m.ID).Error; err != nil {
|
||||
return err
|
||||
|
@ -72,6 +79,7 @@ func (m *Place) Find(db *gorm.DB) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// FirstOrCreate checks wether the place already exists in the database
|
||||
func (m *Place) FirstOrCreate(db *gorm.DB) *Place {
|
||||
mutex.Db.Lock()
|
||||
defer mutex.Db.Unlock()
|
||||
|
@ -83,30 +91,37 @@ func (m *Place) FirstOrCreate(db *gorm.DB) *Place {
|
|||
return m
|
||||
}
|
||||
|
||||
// NoID checks is the place has no id
|
||||
func (m *Place) NoID() bool {
|
||||
return m.ID == ""
|
||||
}
|
||||
|
||||
// Label returns place label
|
||||
func (m *Place) Label() string {
|
||||
return m.LocLabel
|
||||
}
|
||||
|
||||
// City returns place City
|
||||
func (m *Place) City() string {
|
||||
return m.LocCity
|
||||
}
|
||||
|
||||
// State returns place State
|
||||
func (m *Place) State() string {
|
||||
return m.LocState
|
||||
}
|
||||
|
||||
// CountryCode returns place CountryCode
|
||||
func (m *Place) CountryCode() string {
|
||||
return m.LocCountry
|
||||
}
|
||||
|
||||
// CountryName returns place CountryName
|
||||
func (m *Place) CountryName() string {
|
||||
return maps.CountryNames[m.LocCountry]
|
||||
}
|
||||
|
||||
// Notes returns place Notes
|
||||
func (m *Place) Notes() string {
|
||||
return m.LocNotes
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue