package entity import ( "strings" "sync" "time" "github.com/gosimple/slug" "github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/pkg/txt" ) var cameraMutex = sync.Mutex{} // Camera model and make (as extracted from UpdateExif metadata) type Camera struct { ID uint `gorm:"primary_key" json:"ID" yaml:"ID"` CameraSlug string `gorm:"type:VARBINARY(255);unique_index;" json:"Slug" yaml:"-"` CameraName string `gorm:"type:VARCHAR(255);" json:"Name" yaml:"Name"` CameraMake string `gorm:"type:VARCHAR(255);" json:"Make" yaml:"Make,omitempty"` CameraModel string `gorm:"type:VARCHAR(255);" json:"Model" yaml:"Model,omitempty"` CameraType string `gorm:"type:VARCHAR(255);" json:"Type,omitempty" yaml:"Type,omitempty"` CameraDescription string `gorm:"type:TEXT;" json:"Description,omitempty" yaml:"Description,omitempty"` CameraNotes string `gorm:"type:TEXT;" json:"Notes,omitempty" yaml:"Notes,omitempty"` CreatedAt time.Time `json:"-" yaml:"-"` UpdatedAt time.Time `json:"-" yaml:"-"` DeletedAt *time.Time `sql:"index" json:"-" yaml:"-"` } var UnknownCamera = Camera{ CameraSlug: "zz", CameraName: "Unknown", CameraMake: "", CameraModel: "Unknown", } var CameraMakes = map[string]string{ "OLYMPUS": "Olympus", "OLYMPUS CORPORATION": "Olympus", "OLYMPUS DIGITAL CAMERA": "Olympus", "OLYMPUS IMAGING CORP.": "Olympus", "OLYMPUS OPTICAL CO.,LTD": "Olympus", "samsung": "Samsung", } var CameraModels = map[string]string{ "iPhone SE (1st generation)": "iPhone SE", "iPhone SE (2nd generation)": "iPhone SE", "iPhone SE (3rd generation)": "iPhone SE", "WAS-LX1": "P10 lite", "WAS-LX2": "P10 lite", "WAS-LX3": "P10 lite", "WAS-LX1A": "P10 lite", "WAS-LX2J": "P10 lite", "WAS-L03T": "P10 lite", "WAS-AL00": "P10 lite", "WAS-TL10": "P10 lite", "VTR-L29": "P10", "VTR-AL00": "P10", "VTR-TL00": "P10", "VTR-L09": "P10", "EML-AL00": "P20", "EML-L09": "P20", "EML-L09C": "P20", "EML-L29": "P20", "EML-L29C": "P20", "CLT-AL00": "P20 Pro", "CLT-AL01": "P20 Pro", "CLT-TL01": "P20 Pro", "CLT-L09": "P20 Pro", "CLT-L29": "P20 Pro", "ELE-L29": "P30", "ELE-AL00": "P30", "ELE-L04": "P30", "ELE-L09": "P30", "ELE-TL00": "P30", "VOG-L29": "P30 Pro", "VOG-L09": "P30 Pro", "VOG-L04": "P30 Pro", "VOG-AL00": "P30 Pro", "VOG-AL10": "P30 Pro", "VOG-TL00": "P30 Pro", "MAR-L01A": "P30 lite", "MAR-L21A": "P30 lite", "MAR-LX1A": "P30 lite", "MAR-LX1M": "P30 lite", "MAR-LX2": "P30 lite", "MAR-L21MEA": "P30 lite", "MAR-L22A": "P30 lite", "MAR-L22B": "P30 lite", "MAR-LX3A": "P30 lite", "ANA-AN00": "P40", "ANA-TN00": "P40", "ELS-AN00": "P40 Pro", "ELS-TN00": "P40 Pro", "ELS-NX9": "P40 Pro", "ELS-N04": "P40 Pro", "JNY-L21A": "P40 lite", "JNY-L01A": "P40 lite", "JNY-L21B": "P40 lite", "JNY-L22A": "P40 lite", "JNY-L02A": "P40 lite", "JNY-L22B": "P40 lite", "STK-LX1": "Honor 9X", "HLK-AL00": "Honor 9X", "HLK-TL00": "Honor 9X", "SNE-AL00": "Mate 20 lite", "SNE-LX1": "Mate 20 lite", "SNE-LX2": "Mate 20 lite", "SNE-LX3": "Mate 20 lite", "INE-LX2": "Mate 20 lite", "HMA-L29": "Mate 20", "HMA-L09": "Mate 20", "HMA-LX9": "Mate 20", "HMA-AL00": "Mate 20", "HMA-TL00": "Mate 20", "LYA-L09": "Mate 20 Pro", "LYA-L29": "Mate 20 Pro", "LYA-AL00": "Mate 20 Pro", "LYA-AL10": "Mate 20 Pro", "LYA-TL00": "Mate 20 Pro", "LYA-L0C": "Mate 20 Pro", "TAS-L09": "Mate 30", "TAS-L29": "Mate 30", "TAS-AL00": "Mate 30", "TAS-TL00": "Mate 30", "LIO-L09": "Mate 30 Pro", "LIO-L29": "Mate 30 Pro", "LIO-AL00": "Mate 30 Pro", "LIO-TL00": "Mate 30 Pro", } // CreateUnknownCamera initializes the database with an unknown camera if not exists func CreateUnknownCamera() { FirstOrCreateCamera(&UnknownCamera) } // NewCamera creates a camera entity from a model name and a make name. func NewCamera(modelName string, makeName string) *Camera { modelName = txt.Clip(modelName, txt.ClipDefault) makeName = txt.Clip(makeName, txt.ClipDefault) if modelName == "" && makeName == "" { return &UnknownCamera } else if strings.HasPrefix(modelName, makeName) { modelName = strings.TrimSpace(modelName[len(makeName):]) } if n, ok := CameraMakes[makeName]; ok { makeName = n } if n, ok := CameraModels[modelName]; ok { modelName = n } var name []string if makeName != "" { name = append(name, makeName) } if modelName != "" { name = append(name, modelName) } cameraName := strings.Join(name, " ") cameraSlug := slug.Make(txt.Clip(cameraName, txt.ClipSlug)) result := &Camera{ CameraSlug: cameraSlug, CameraName: cameraName, CameraMake: makeName, CameraModel: modelName, } return result } // Create inserts a new row to the database. func (m *Camera) Create() error { cameraMutex.Lock() defer cameraMutex.Unlock() return Db().Create(m).Error } // FirstOrCreateCamera returns the existing row, inserts a new row or nil in case of errors. func FirstOrCreateCamera(m *Camera) *Camera { if m.CameraSlug == "" { return &UnknownCamera } result := Camera{} if res := Db().Where("camera_slug = ?", m.CameraSlug).First(&result); res.Error == nil { return &result } else if err := m.Create(); err == nil { if !m.Unknown() { event.EntitiesCreated("cameras", []*Camera{m}) event.Publish("count.cameras", event.Data{ "count": 1, }) } return m } else if res := Db().Where("camera_slug = ?", m.CameraSlug).First(&result); res.Error == nil { return &result } else { log.Errorf("camera: %s (create %s)", err.Error(), txt.Quote(m.String())) } return &UnknownCamera } // String returns an identifier that can be used in logs. func (m *Camera) String() string { return m.CameraName } // Unknown returns true if the camera is not a known make or model. func (m *Camera) Unknown() bool { return m.CameraSlug == "" || m.CameraSlug == UnknownCamera.CameraSlug }