People: Add SubjectNames() to file entity #22
This commit is contained in:
parent
a5f8e6149f
commit
1be409d654
14 changed files with 202 additions and 50 deletions
|
@ -69,7 +69,7 @@ type File struct {
|
|||
DeletedAt *time.Time `sql:"index" json:"DeletedAt,omitempty" yaml:"-"`
|
||||
Share []FileShare `json:"-" yaml:"-"`
|
||||
Sync []FileSync `json:"-" yaml:"-"`
|
||||
Markers Markers `json:"Markers,omitempty" yaml:"-"`
|
||||
markers *Markers
|
||||
}
|
||||
|
||||
type FileInfos struct {
|
||||
|
@ -254,7 +254,7 @@ func (m *File) Create() error {
|
|||
return err
|
||||
}
|
||||
|
||||
if err := m.Markers.Save(m.FileUID); err != nil {
|
||||
if err := m.Markers().Save(m.FileUID); err != nil {
|
||||
log.Errorf("file: %s (create markers for %s)", err, m.FileUID)
|
||||
return err
|
||||
}
|
||||
|
@ -282,7 +282,7 @@ func (m *File) Save() error {
|
|||
return err
|
||||
}
|
||||
|
||||
if err := m.Markers.Save(m.FileUID); err != nil {
|
||||
if err := m.Markers().Save(m.FileUID); err != nil {
|
||||
log.Errorf("file: %s (save markers for %s)", err, m.FileUID)
|
||||
return err
|
||||
}
|
||||
|
@ -406,15 +406,18 @@ func (m *File) AddFaces(faces face.Faces) {
|
|||
|
||||
// AddFace adds a face marker to the file.
|
||||
func (m *File) AddFace(f face.Face, subjectUID string) {
|
||||
marker := NewFaceMarker(f, m.FileUID, subjectUID)
|
||||
if !m.Markers.Contains(*marker) {
|
||||
m.Markers = append(m.Markers, *marker)
|
||||
marker := *NewFaceMarker(f, m.FileUID, subjectUID)
|
||||
|
||||
if markers := m.Markers(); !markers.Contains(marker) {
|
||||
markers.Append(marker)
|
||||
}
|
||||
}
|
||||
|
||||
// FaceCount returns the current number of valid faces detected.
|
||||
func (m *File) FaceCount() (c int) {
|
||||
if err := Db().Model(Marker{}).Where("file_uid = ? AND marker_invalid = 0", m.FileUID).
|
||||
if err := Db().Model(Marker{}).
|
||||
Where("file_uid = ? AND marker_type = ?", m.FileUID, MarkerFace).
|
||||
Where("marker_invalid = 0").
|
||||
Count(&c).Error; err != nil {
|
||||
log.Errorf("file: %s (count faces)", err)
|
||||
return 0
|
||||
|
@ -423,11 +426,23 @@ func (m *File) FaceCount() (c int) {
|
|||
}
|
||||
}
|
||||
|
||||
// PreloadMarkers loads existing file markers.
|
||||
func (m *File) PreloadMarkers() {
|
||||
// Markers finds and returns existing file markers.
|
||||
func (m *File) Markers() *Markers {
|
||||
if m.markers != nil {
|
||||
return m.markers
|
||||
}
|
||||
|
||||
if res, err := FindMarkers(m.FileUID); err != nil {
|
||||
log.Warnf("file: %s (load markers)", err)
|
||||
m.markers = &Markers{}
|
||||
} else {
|
||||
m.Markers = res
|
||||
m.markers = &res
|
||||
}
|
||||
|
||||
return m.markers
|
||||
}
|
||||
|
||||
// SubjectNames returns all known subject names.
|
||||
func (m *File) SubjectNames() []string {
|
||||
return m.Markers().SubjectNames()
|
||||
}
|
||||
|
|
83
internal/entity/file_json.go
Normal file
83
internal/entity/file_json.go
Normal file
|
@ -0,0 +1,83 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
)
|
||||
|
||||
// MarshalJSON returns the JSON encoding.
|
||||
func (m *File) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(&struct {
|
||||
UID string
|
||||
PhotoUID string
|
||||
InstanceID string `json:",omitempty"`
|
||||
Name string
|
||||
Root string
|
||||
OriginalName string `json:",omitempty"`
|
||||
Hash string
|
||||
Size int64
|
||||
Codec string `json:",omitempty"`
|
||||
Type string
|
||||
Mime string `json:",omitempty"`
|
||||
Primary bool
|
||||
Sidecar bool `json:",omitempty"`
|
||||
Missing bool `json:",omitempty"`
|
||||
Portrait bool `json:",omitempty"`
|
||||
Video bool `json:",omitempty"`
|
||||
Duration time.Duration `json:",omitempty"`
|
||||
Width int `json:",omitempty"`
|
||||
Height int `json:",omitempty"`
|
||||
Orientation int `json:",omitempty"`
|
||||
Projection string `json:",omitempty"`
|
||||
AspectRatio float32 `json:",omitempty"`
|
||||
MainColor string `json:",omitempty"`
|
||||
Colors string `json:",omitempty"`
|
||||
Luminance string `json:",omitempty"`
|
||||
Diff uint32 `json:",omitempty"`
|
||||
Chroma uint8 `json:",omitempty"`
|
||||
Error string `json:",omitempty"`
|
||||
ModTime int64 `json:",omitempty"`
|
||||
CreatedAt time.Time `json:",omitempty"`
|
||||
CreatedIn int64 `json:",omitempty"`
|
||||
UpdatedAt time.Time `json:",omitempty"`
|
||||
UpdatedIn int64 `json:",omitempty"`
|
||||
DeletedAt *time.Time `json:",omitempty"`
|
||||
Markers *Markers `json:",omitempty"`
|
||||
}{
|
||||
UID: m.FileUID,
|
||||
PhotoUID: m.PhotoUID,
|
||||
InstanceID: m.InstanceID,
|
||||
Name: m.FileName,
|
||||
Root: m.FileRoot,
|
||||
OriginalName: m.OriginalName,
|
||||
Hash: m.FileHash,
|
||||
Size: m.FileSize,
|
||||
Codec: m.FileCodec,
|
||||
Type: m.FileType,
|
||||
Mime: m.FileMime,
|
||||
Primary: m.FilePrimary,
|
||||
Sidecar: m.FileSidecar,
|
||||
Missing: m.FileMissing,
|
||||
Portrait: m.FilePortrait,
|
||||
Video: m.FileVideo,
|
||||
Duration: m.FileDuration,
|
||||
Width: m.FileWidth,
|
||||
Height: m.FileHeight,
|
||||
Orientation: m.FileOrientation,
|
||||
Projection: m.FileProjection,
|
||||
AspectRatio: m.FileAspectRatio,
|
||||
MainColor: m.FileMainColor,
|
||||
Colors: m.FileColors,
|
||||
Luminance: m.FileLuminance,
|
||||
Diff: m.FileDiff,
|
||||
Chroma: m.FileChroma,
|
||||
Error: m.FileError,
|
||||
ModTime: m.ModTime,
|
||||
CreatedAt: m.CreatedAt,
|
||||
CreatedIn: m.CreatedIn,
|
||||
UpdatedAt: m.UpdatedAt,
|
||||
UpdatedIn: m.UpdatedIn,
|
||||
DeletedAt: m.DeletedAt,
|
||||
Markers: m.Markers(),
|
||||
})
|
||||
}
|
13
internal/entity/file_json_test.go
Normal file
13
internal/entity/file_json_test.go
Normal file
|
@ -0,0 +1,13 @@
|
|||
package entity
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestFile_MarshalJSON(t *testing.T) {
|
||||
if m := FileFixtures.Pointer("Video.mp4"); m == nil {
|
||||
t.Fatal("must not be nil")
|
||||
} else if j, err := m.MarshalJSON(); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
t.Logf("json: %s", j)
|
||||
}
|
||||
}
|
|
@ -446,7 +446,7 @@ func TestFile_AddFaces(t *testing.T) {
|
|||
|
||||
assert.Equal(t, false, file.FileMissing)
|
||||
assert.NotEmpty(t, file.FileUID)
|
||||
assert.NotEmpty(t, file.Markers)
|
||||
assert.NotEmpty(t, file.Markers())
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -489,3 +489,15 @@ func TestFile_Rename(t *testing.T) {
|
|||
assert.Equal(t, "27900704_070228_D6D51B6C", p.PhotoName)
|
||||
})
|
||||
}
|
||||
|
||||
func TestFile_SubjectNames(t *testing.T) {
|
||||
f := FileFixtures.Get("Video.mp4")
|
||||
names := f.SubjectNames()
|
||||
|
||||
assert.Len(t, names, 1)
|
||||
if len(names) != 1 {
|
||||
t.Fatal("there should be one name")
|
||||
} else {
|
||||
assert.Equal(t, "Actress A", names[0])
|
||||
}
|
||||
}
|
||||
|
|
|
@ -331,6 +331,17 @@ func (m *Marker) Embeddings() Embeddings {
|
|||
return m.embeddings
|
||||
}
|
||||
|
||||
// SubjectName returns the matching subject's name.
|
||||
func (m *Marker) SubjectName() string {
|
||||
if m.MarkerName != "" {
|
||||
return m.MarkerName
|
||||
} else if s := m.Subject(); s != nil {
|
||||
return s.SubjectName
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// Subject returns the matching subject or nil.
|
||||
func (m *Marker) Subject() (subj *Subject) {
|
||||
if m.subject != nil {
|
||||
|
|
|
@ -194,6 +194,7 @@ var MarkerFixtures = MarkerMap{
|
|||
MarkerUID: "mt9k3pw1wowuy999",
|
||||
FileUID: "ft71s39w45bnlqdw",
|
||||
FaceID: FaceFixtures.Get("actress-1").ID,
|
||||
FaceThumb: "ce2e128c45abb3aa73b3eecb2831cd1761fe2b4f-178-559-68",
|
||||
FaceDist: 0.26852392873736236,
|
||||
SubjectSrc: SrcManual,
|
||||
SubjectUID: SubjectFixtures.Get("actress-1").SubjectUID,
|
||||
|
@ -213,6 +214,7 @@ var MarkerFixtures = MarkerMap{
|
|||
MarkerUID: "mt9k3pw1wowu1000",
|
||||
FileUID: "ft72s39w45bnlqdw",
|
||||
FaceID: FaceFixtures.Get("actress-1").ID,
|
||||
FaceThumb: "ce2e128c45abb3aa73b3eecb2831cd1761fe2b4f-1-2-3",
|
||||
FaceDist: 0.4507357278575355,
|
||||
SubjectSrc: "",
|
||||
SubjectUID: SubjectFixtures.Get("actress-1").SubjectUID,
|
||||
|
@ -232,6 +234,7 @@ var MarkerFixtures = MarkerMap{
|
|||
MarkerUID: "mt9k3pw1wowu1001",
|
||||
FileUID: "ft2es39q45bnlqd0",
|
||||
FaceID: FaceFixtures.Get("actress-1").ID,
|
||||
FaceThumb: "ce2e128c45abb3aa73b3eecb2831cd1761fe2b4f-4-5-6",
|
||||
FaceDist: 0.5099754448545762,
|
||||
SubjectSrc: "",
|
||||
SubjectUID: SubjectFixtures.Get("actress-1").SubjectUID,
|
||||
|
|
|
@ -22,18 +22,18 @@ func (m *Marker) MarshalJSON() ([]byte, error) {
|
|||
Type string
|
||||
Src string
|
||||
Name string
|
||||
SubjectUID string
|
||||
SubjectSrc string `json:",omitempty"`
|
||||
FaceID string `json:",omitempty"`
|
||||
FaceThumb string
|
||||
X float32
|
||||
Y float32
|
||||
W float32
|
||||
H float32
|
||||
Size int
|
||||
Score int
|
||||
Review bool
|
||||
Invalid bool
|
||||
SubjectUID string `json:",omitempty"`
|
||||
SubjectSrc string `json:",omitempty"`
|
||||
FaceID string `json:",omitempty"`
|
||||
FaceThumb string `json:",omitempty"`
|
||||
Size int `json:",omitempty"`
|
||||
Score int `json:",omitempty"`
|
||||
Review bool `json:",omitempty"`
|
||||
Invalid bool `json:",omitempty"`
|
||||
CreatedAt time.Time
|
||||
}{
|
||||
UID: m.MarkerUID,
|
||||
|
@ -41,14 +41,14 @@ func (m *Marker) MarshalJSON() ([]byte, error) {
|
|||
Type: m.MarkerType,
|
||||
Src: m.MarkerSrc,
|
||||
Name: name,
|
||||
SubjectUID: m.SubjectUID,
|
||||
SubjectSrc: m.SubjectSrc,
|
||||
FaceID: m.FaceID,
|
||||
FaceThumb: m.FaceThumb,
|
||||
X: m.X,
|
||||
Y: m.Y,
|
||||
W: m.W,
|
||||
H: m.H,
|
||||
SubjectUID: m.SubjectUID,
|
||||
SubjectSrc: m.SubjectSrc,
|
||||
FaceID: m.FaceID,
|
||||
FaceThumb: m.FaceThumb,
|
||||
Size: m.Size,
|
||||
Score: m.Score,
|
||||
Review: m.Review,
|
||||
|
|
15
internal/entity/marker_json_test.go
Normal file
15
internal/entity/marker_json_test.go
Normal file
|
@ -0,0 +1,15 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMarker_MarshalJSON(t *testing.T) {
|
||||
if m := MarkerFixtures.Pointer("actor-a-2"); m == nil {
|
||||
t.Fatal("must not be nil")
|
||||
} else if j, err := m.MarshalJSON(); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
t.Logf("json: %s", j)
|
||||
}
|
||||
}
|
|
@ -12,16 +12,6 @@ func TestMarker_TableName(t *testing.T) {
|
|||
assert.Contains(t, m.TableName(), "markers")
|
||||
}
|
||||
|
||||
func TestMarker_MarshalJSON(t *testing.T) {
|
||||
if m := MarkerFixtures.Pointer("actor-a-2"); m == nil {
|
||||
t.Fatal("must not be nil")
|
||||
} else if j, err := m.MarshalJSON(); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
t.Logf("json: %s", j)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewMarker(t *testing.T) {
|
||||
m := NewMarker("ft8es39w45bnlqdw", "lt9k3pw1wowuy3c3", SrcImage, MarkerLabel, 0.308333, 0.206944, 0.355556, 0.355556)
|
||||
assert.IsType(t, &Marker{}, m)
|
||||
|
@ -193,7 +183,7 @@ func TestMarker_Save(t *testing.T) {
|
|||
|
||||
p := PhotoFixtures.Get("19800101_000002_D640C559")
|
||||
assert.Empty(t, p.Files)
|
||||
p.PreloadFiles(true)
|
||||
p.PreloadFiles()
|
||||
assert.NotEmpty(t, p.Files)
|
||||
|
||||
// t.Logf("FILES: %#v", p.Files)
|
||||
|
|
|
@ -41,13 +41,31 @@ func (m Markers) FaceCount() (faces int) {
|
|||
return faces
|
||||
}
|
||||
|
||||
// SubjectNames returns known subject names.
|
||||
func (m Markers) SubjectNames() (names []string) {
|
||||
for _, marker := range m {
|
||||
if marker.MarkerInvalid || marker.MarkerType != MarkerFace {
|
||||
continue
|
||||
} else if n := marker.SubjectName(); n != "" {
|
||||
names = append(names, n)
|
||||
}
|
||||
}
|
||||
|
||||
return names
|
||||
}
|
||||
|
||||
// Append adds a marker.
|
||||
func (m *Markers) Append(marker Marker) {
|
||||
*m = append(*m, marker)
|
||||
}
|
||||
|
||||
// FindMarkers returns up to 1000 markers for a given file uid.
|
||||
func FindMarkers(fileUID string) (Markers, error) {
|
||||
m := Markers{}
|
||||
|
||||
err := Db().
|
||||
Where(`file_uid = ?`, fileUID).
|
||||
Order("marker_uid").
|
||||
Order("x").
|
||||
Offset(0).Limit(1000).
|
||||
Find(&m).Error
|
||||
|
||||
|
|
|
@ -444,7 +444,7 @@ func (m *Photo) IndexKeywords() error {
|
|||
}
|
||||
|
||||
// PreloadFiles prepares gorm scope to retrieve photo file
|
||||
func (m *Photo) PreloadFiles(markers bool) {
|
||||
func (m *Photo) PreloadFiles() {
|
||||
q := Db().
|
||||
Table("files").
|
||||
Select(`files.*`).
|
||||
|
@ -452,12 +452,6 @@ func (m *Photo) PreloadFiles(markers bool) {
|
|||
Order("files.file_name DESC")
|
||||
|
||||
logError(q.Scan(&m.Files))
|
||||
|
||||
if markers {
|
||||
for i := range m.Files {
|
||||
m.Files[i].PreloadMarkers()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PreloadKeywords prepares gorm scope to retrieve photo keywords
|
||||
|
@ -485,7 +479,7 @@ func (m *Photo) PreloadAlbums() {
|
|||
|
||||
// PreloadMany prepares gorm scope to retrieve photo file, albums and keywords
|
||||
func (m *Photo) PreloadMany() {
|
||||
m.PreloadFiles(true)
|
||||
m.PreloadFiles()
|
||||
m.PreloadKeywords()
|
||||
m.PreloadAlbums()
|
||||
}
|
||||
|
|
|
@ -145,7 +145,7 @@ func TestPhoto_PreloadFiles(t *testing.T) {
|
|||
t.Run("success", func(t *testing.T) {
|
||||
m := PhotoFixtures.Get("Photo01")
|
||||
assert.Empty(t, m.Files)
|
||||
m.PreloadFiles(false)
|
||||
m.PreloadFiles()
|
||||
assert.NotEmpty(t, m.Files)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
func TestPhoto_Yaml(t *testing.T) {
|
||||
t.Run("create from fixture", func(t *testing.T) {
|
||||
m := PhotoFixtures.Get("Photo01")
|
||||
m.PreloadFiles(true)
|
||||
m.PreloadFiles()
|
||||
result, err := m.Yaml()
|
||||
|
||||
if err != nil {
|
||||
|
@ -25,7 +25,7 @@ func TestPhoto_Yaml(t *testing.T) {
|
|||
func TestPhoto_SaveAsYaml(t *testing.T) {
|
||||
t.Run("create from fixture", func(t *testing.T) {
|
||||
m := PhotoFixtures.Get("Photo01")
|
||||
m.PreloadFiles(true)
|
||||
m.PreloadFiles()
|
||||
|
||||
fileName := filepath.Join(os.TempDir(), ".photoprism_test.yml")
|
||||
|
||||
|
@ -46,7 +46,7 @@ func TestPhoto_SaveAsYaml(t *testing.T) {
|
|||
func TestPhoto_YamlFileName(t *testing.T) {
|
||||
t.Run("create from fixture", func(t *testing.T) {
|
||||
m := PhotoFixtures.Get("Photo01")
|
||||
m.PreloadFiles(false)
|
||||
m.PreloadFiles()
|
||||
assert.Equal(t, "xxx/2790/02/yyy/Photo01.yml", m.YamlFileName("xxx", "yyy"))
|
||||
|
||||
if err := os.RemoveAll("xxx"); err != nil {
|
||||
|
|
|
@ -603,13 +603,11 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) (
|
|||
|
||||
photo.AddLabels(classify.FaceLabels(faces, entity.SrcImage))
|
||||
|
||||
file.PreloadMarkers()
|
||||
|
||||
if len(faces) > 0 {
|
||||
file.AddFaces(faces)
|
||||
}
|
||||
|
||||
photo.PhotoFaces = file.Markers.FaceCount()
|
||||
photo.PhotoFaces = file.Markers().FaceCount()
|
||||
}
|
||||
|
||||
labels := photo.ClassifyLabels()
|
||||
|
|
Loading…
Reference in a new issue