diff --git a/api/gqlgen.yml b/api/gqlgen.yml
index 73a286af..0ca6f79b 100644
--- a/api/gqlgen.yml
+++ b/api/gqlgen.yml
@@ -48,6 +48,9 @@ models:
model: github.com/photoview/photoview/api/graphql/models.FaceGroup
ImageFace:
model: github.com/photoview/photoview/api/graphql/models.ImageFace
+ fields:
+ faceGroup:
+ resolver: true
FaceRectangle:
model: github.com/photoview/photoview/api/graphql/models.FaceRectangle
SiteInfo:
diff --git a/api/graphql/generated.go b/api/graphql/generated.go
index d8e468ed..5c01fe64 100644
--- a/api/graphql/generated.go
+++ b/api/graphql/generated.go
@@ -39,6 +39,7 @@ type Config struct {
type ResolverRoot interface {
Album() AlbumResolver
+ ImageFace() ImageFaceResolver
Media() MediaResolver
Mutation() MutationResolver
Query() QueryResolver
@@ -85,6 +86,7 @@ type ComplexityRoot struct {
}
ImageFace struct {
+ FaceGroup func(childComplexity int) int
ID func(childComplexity int) int
Media func(childComplexity int) int
Rectangle func(childComplexity int) int
@@ -258,6 +260,9 @@ type AlbumResolver interface {
Path(ctx context.Context, obj *models.Album) ([]*models.Album, error)
Shares(ctx context.Context, obj *models.Album) ([]*models.ShareToken, error)
}
+type ImageFaceResolver interface {
+ FaceGroup(ctx context.Context, obj *models.ImageFace) (*models.FaceGroup, error)
+}
type MediaResolver interface {
Thumbnail(ctx context.Context, obj *models.Media) (*models.MediaURL, error)
HighRes(ctx context.Context, obj *models.Media) (*models.MediaURL, error)
@@ -486,6 +491,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.FaceRectangle.MinY(childComplexity), true
+ case "ImageFace.faceGroup":
+ if e.complexity.ImageFace.FaceGroup == nil {
+ break
+ }
+
+ return e.complexity.ImageFace.FaceGroup(childComplexity), true
+
case "ImageFace.id":
if e.complexity.ImageFace.ID == nil {
break
@@ -1857,6 +1869,7 @@ type ImageFace {
id: ID!
media: Media!
rectangle: FaceRectangle
+ faceGroup: FaceGroup!
}
type FaceRectangle {
@@ -3489,6 +3502,41 @@ func (ec *executionContext) _ImageFace_rectangle(ctx context.Context, field grap
return ec.marshalOFaceRectangle2githubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐFaceRectangle(ctx, field.Selections, res)
}
+func (ec *executionContext) _ImageFace_faceGroup(ctx context.Context, field graphql.CollectedField, obj *models.ImageFace) (ret graphql.Marshaler) {
+ defer func() {
+ if r := recover(); r != nil {
+ ec.Error(ctx, ec.Recover(ctx, r))
+ ret = graphql.Null
+ }
+ }()
+ fc := &graphql.FieldContext{
+ Object: "ImageFace",
+ Field: field,
+ Args: nil,
+ IsMethod: true,
+ IsResolver: true,
+ }
+
+ ctx = graphql.WithFieldContext(ctx, fc)
+ resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+ ctx = rctx // use context from middleware stack in children
+ return ec.resolvers.ImageFace().FaceGroup(rctx, obj)
+ })
+ if err != nil {
+ ec.Error(ctx, err)
+ return graphql.Null
+ }
+ if resTmp == nil {
+ if !graphql.HasFieldError(ctx, fc) {
+ ec.Errorf(ctx, "must not be null")
+ }
+ return graphql.Null
+ }
+ res := resTmp.(*models.FaceGroup)
+ fc.Result = res
+ return ec.marshalNFaceGroup2ᚖgithubᚗcomᚋphotoviewᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐFaceGroup(ctx, field.Selections, res)
+}
+
func (ec *executionContext) _Media_id(ctx context.Context, field graphql.CollectedField, obj *models.Media) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@@ -9188,15 +9236,29 @@ func (ec *executionContext) _ImageFace(ctx context.Context, sel ast.SelectionSet
case "id":
out.Values[i] = ec._ImageFace_id(ctx, field, obj)
if out.Values[i] == graphql.Null {
- invalids++
+ atomic.AddUint32(&invalids, 1)
}
case "media":
out.Values[i] = ec._ImageFace_media(ctx, field, obj)
if out.Values[i] == graphql.Null {
- invalids++
+ atomic.AddUint32(&invalids, 1)
}
case "rectangle":
out.Values[i] = ec._ImageFace_rectangle(ctx, field, obj)
+ case "faceGroup":
+ field := field
+ out.Concurrently(i, func() (res graphql.Marshaler) {
+ defer func() {
+ if r := recover(); r != nil {
+ ec.Error(ctx, ec.Recover(ctx, r))
+ }
+ }()
+ res = ec._ImageFace_faceGroup(ctx, field, obj)
+ if res == graphql.Null {
+ atomic.AddUint32(&invalids, 1)
+ }
+ return res
+ })
default:
panic("unknown field " + strconv.Quote(field.Name))
}
diff --git a/api/graphql/models/face_detection.go b/api/graphql/models/face_detection.go
index b4661a82..f3918dbb 100644
--- a/api/graphql/models/face_detection.go
+++ b/api/graphql/models/face_detection.go
@@ -21,7 +21,8 @@ type FaceGroup struct {
type ImageFace struct {
Model
- FaceGroupID int `gorm:"not null;index"`
+ FaceGroupID int `gorm:"not null;index"`
+ FaceGroup *FaceGroup
MediaID int `gorm:"not null;index"`
Media Media `gorm:"constraint:OnDelete:CASCADE;"`
Descriptor FaceDescriptor `gorm:"not null"`
diff --git a/api/graphql/resolvers/faces.go b/api/graphql/resolvers/faces.go
index fa4ff040..dbe77178 100644
--- a/api/graphql/resolvers/faces.go
+++ b/api/graphql/resolvers/faces.go
@@ -3,6 +3,7 @@ package resolvers
import (
"context"
+ api "github.com/photoview/photoview/api/graphql"
"github.com/photoview/photoview/api/graphql/auth"
"github.com/photoview/photoview/api/graphql/models"
"github.com/photoview/photoview/api/scanner/face_detection"
@@ -10,6 +11,29 @@ import (
"gorm.io/gorm"
)
+type imageFaceResolver struct {
+ *Resolver
+}
+
+func (r *Resolver) ImageFace() api.ImageFaceResolver {
+ return imageFaceResolver{r}
+}
+
+func (r imageFaceResolver) FaceGroup(ctx context.Context, obj *models.ImageFace) (*models.FaceGroup, error) {
+ if obj.FaceGroup != nil {
+ return obj.FaceGroup, nil
+ }
+
+ var faceGroup models.FaceGroup
+ if err := r.Database.Model(&obj).Association("FaceGroup").Find(&faceGroup); err != nil {
+ return nil, err
+ }
+
+ obj.FaceGroup = &faceGroup
+
+ return &faceGroup, nil
+}
+
func (r *queryResolver) MyFaceGroups(ctx context.Context, paginate *models.Pagination) ([]*models.FaceGroup, error) {
user := auth.UserFromContext(ctx)
if user == nil {
diff --git a/api/graphql/schema.graphql b/api/graphql/schema.graphql
index cc10c9bd..36bb8ebd 100644
--- a/api/graphql/schema.graphql
+++ b/api/graphql/schema.graphql
@@ -338,6 +338,7 @@ type ImageFace {
id: ID!
media: Media!
rectangle: FaceRectangle
+ faceGroup: FaceGroup!
}
type FaceRectangle {
diff --git a/api/scanner/face_detection/face_detector.go b/api/scanner/face_detection/face_detector.go
index 9cab8885..da4ab32e 100644
--- a/api/scanner/face_detection/face_detector.go
+++ b/api/scanner/face_detection/face_detector.go
@@ -104,7 +104,7 @@ func (fd *FaceDetector) DetectFaces(media *models.Media) error {
}
func (fd *FaceDetector) classifyDescriptor(descriptor face.Descriptor) int32 {
- return int32(fd.rec.ClassifyThreshold(descriptor, 0.2))
+ return int32(fd.rec.ClassifyThreshold(descriptor, 0.3))
}
func (fd *FaceDetector) classifyFace(face *face.Face, media *models.Media, imagePath string) error {
diff --git a/api/scanner/media_type.go b/api/scanner/media_type.go
index bc77a0f8..46f9e2ea 100644
--- a/api/scanner/media_type.go
+++ b/api/scanner/media_type.go
@@ -150,6 +150,7 @@ var fileExtensions = map[string]MediaType{
".arw": TypeARW,
".sr2": TypeSR2,
".srf": TypeSRF,
+ ".srw": TypeSRW,
".cr2": TypeCR2,
".crw": TypeCRW,
".erf": TypeERF,
@@ -159,15 +160,22 @@ var fileExtensions = map[string]MediaType{
".mrw": TypeMRW,
".nef": TypeNEF,
".nrw": TypeNRW,
+ ".mdc": TypeMDC,
+ ".mef": TypeMEF,
".orf": TypeORF,
".pef": TypePEF,
".raf": TypeRAF,
".raw": TypeRAW,
+ ".rw2": TypeRW2,
".dcs": TypeDCS,
".drf": TypeDRF,
".gpr": TypeGPR,
".3fr": Type3FR,
".fff": TypeFFF,
+ ".cap": TypeCap,
+ ".iiq": TypeIIQ,
+ ".mos": TypeMOS,
+ ".rwl": TypeRWL,
// Video formats
".mp4": TypeMP4,
diff --git a/ui/src/components/facesOverlay/FacesOverlay.js b/ui/src/components/facesOverlay/FacesOverlay.js
index d6536673..74a69644 100644
--- a/ui/src/components/facesOverlay/FacesOverlay.js
+++ b/ui/src/components/facesOverlay/FacesOverlay.js
@@ -1,19 +1,28 @@
import PropTypes from 'prop-types'
import React from 'react'
+import { Link } from 'react-router-dom'
import styled from 'styled-components'
-const FaceBoxStyle = styled.div`
+const FaceBoxStyle = styled(Link)`
box-shadow: inset 0 0 2px 1px rgba(0, 0, 0, 0.3), 0 0 0 1px rgb(255, 255, 255);
border-radius: 50%;
position: absolute;
- top: ${({ minY }) => minY * 100}%;
- bottom: ${({ maxY }) => (1 - maxY) * 100}%;
- left: ${({ minX }) => minX * 100}%;
- right: ${({ maxX }) => (1 - maxX) * 100}%;
+ top: ${({ $minY }) => $minY * 100}%;
+ bottom: ${({ $maxY }) => (1 - $maxY) * 100}%;
+ left: ${({ $minX }) => $minX * 100}%;
+ right: ${({ $maxX }) => (1 - $maxX) * 100}%;
`
const FaceBox = ({ face /*media*/ }) => {
- return
+ return (
+
+ )
}
FaceBox.propTypes = {
diff --git a/ui/src/components/sidebar/MediaSidebar.js b/ui/src/components/sidebar/MediaSidebar.js
index 4e88a0db..906d29b5 100644
--- a/ui/src/components/sidebar/MediaSidebar.js
+++ b/ui/src/components/sidebar/MediaSidebar.js
@@ -62,6 +62,9 @@ const mediaQuery = gql`
minY
maxY
}
+ faceGroup {
+ id
+ }
}
}
}