mirror of
https://git.vectorsigma.ru/public/photoview.git
synced 2026-08-03 19:49:16 +00:00
Various improvements to faces UI
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -338,6 +338,7 @@ type ImageFace {
|
||||
id: ID!
|
||||
media: Media!
|
||||
rectangle: FaceRectangle
|
||||
faceGroup: FaceGroup!
|
||||
}
|
||||
|
||||
type FaceRectangle {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 <FaceBoxStyle {...face.rectangle}></FaceBoxStyle>
|
||||
return (
|
||||
<FaceBoxStyle
|
||||
to={`/people/${face.faceGroup.id}`}
|
||||
$minX={face.rectangle.minX}
|
||||
$maxX={face.rectangle.maxX}
|
||||
$minY={face.rectangle.minY}
|
||||
$maxY={face.rectangle.maxY}
|
||||
></FaceBoxStyle>
|
||||
)
|
||||
}
|
||||
|
||||
FaceBox.propTypes = {
|
||||
|
||||
@@ -62,6 +62,9 @@ const mediaQuery = gql`
|
||||
minY
|
||||
maxY
|
||||
}
|
||||
faceGroup {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user