Merge pull request #488 from photoview/feature-env-vars
Add environment variables to disable optional features
This commit is contained in:
commit
4af3d11db4
|
@ -45,6 +45,7 @@ type ResolverRoot interface {
|
||||||
Mutation() MutationResolver
|
Mutation() MutationResolver
|
||||||
Query() QueryResolver
|
Query() QueryResolver
|
||||||
ShareToken() ShareTokenResolver
|
ShareToken() ShareTokenResolver
|
||||||
|
SiteInfo() SiteInfoResolver
|
||||||
Subscription() SubscriptionResolver
|
Subscription() SubscriptionResolver
|
||||||
User() UserResolver
|
User() UserResolver
|
||||||
}
|
}
|
||||||
|
@ -220,6 +221,7 @@ type ComplexityRoot struct {
|
||||||
|
|
||||||
SiteInfo struct {
|
SiteInfo struct {
|
||||||
ConcurrentWorkers func(childComplexity int) int
|
ConcurrentWorkers func(childComplexity int) int
|
||||||
|
FaceDetectionEnabled func(childComplexity int) int
|
||||||
InitialSetup func(childComplexity int) int
|
InitialSetup func(childComplexity int) int
|
||||||
PeriodicScanInterval func(childComplexity int) int
|
PeriodicScanInterval func(childComplexity int) int
|
||||||
}
|
}
|
||||||
|
@ -338,6 +340,9 @@ type QueryResolver interface {
|
||||||
type ShareTokenResolver interface {
|
type ShareTokenResolver interface {
|
||||||
HasPassword(ctx context.Context, obj *models.ShareToken) (bool, error)
|
HasPassword(ctx context.Context, obj *models.ShareToken) (bool, error)
|
||||||
}
|
}
|
||||||
|
type SiteInfoResolver interface {
|
||||||
|
FaceDetectionEnabled(ctx context.Context, obj *models.SiteInfo) (bool, error)
|
||||||
|
}
|
||||||
type SubscriptionResolver interface {
|
type SubscriptionResolver interface {
|
||||||
Notification(ctx context.Context) (<-chan *models.Notification, error)
|
Notification(ctx context.Context) (<-chan *models.Notification, error)
|
||||||
}
|
}
|
||||||
|
@ -1369,6 +1374,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
||||||
|
|
||||||
return e.complexity.SiteInfo.ConcurrentWorkers(childComplexity), true
|
return e.complexity.SiteInfo.ConcurrentWorkers(childComplexity), true
|
||||||
|
|
||||||
|
case "SiteInfo.faceDetectionEnabled":
|
||||||
|
if e.complexity.SiteInfo.FaceDetectionEnabled == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.complexity.SiteInfo.FaceDetectionEnabled(childComplexity), true
|
||||||
|
|
||||||
case "SiteInfo.initialSetup":
|
case "SiteInfo.initialSetup":
|
||||||
if e.complexity.SiteInfo.InitialSetup == nil {
|
if e.complexity.SiteInfo.InitialSetup == nil {
|
||||||
break
|
break
|
||||||
|
@ -1821,7 +1833,10 @@ type ShareToken {
|
||||||
|
|
||||||
"General information about the site"
|
"General information about the site"
|
||||||
type SiteInfo {
|
type SiteInfo {
|
||||||
|
"Whether or not the initial setup wizard should be shown"
|
||||||
initialSetup: Boolean!
|
initialSetup: Boolean!
|
||||||
|
"Whether or not face detection is enabled and working"
|
||||||
|
faceDetectionEnabled: Boolean!
|
||||||
"How often automatic scans should be initiated in seconds"
|
"How often automatic scans should be initiated in seconds"
|
||||||
periodicScanInterval: Int! @isAdmin
|
periodicScanInterval: Int! @isAdmin
|
||||||
"How many max concurrent scanner jobs that should run at once"
|
"How many max concurrent scanner jobs that should run at once"
|
||||||
|
@ -7862,6 +7877,41 @@ func (ec *executionContext) _SiteInfo_initialSetup(ctx context.Context, field gr
|
||||||
return ec.marshalNBoolean2bool(ctx, field.Selections, res)
|
return ec.marshalNBoolean2bool(ctx, field.Selections, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) _SiteInfo_faceDetectionEnabled(ctx context.Context, field graphql.CollectedField, obj *models.SiteInfo) (ret graphql.Marshaler) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
ec.Error(ctx, ec.Recover(ctx, r))
|
||||||
|
ret = graphql.Null
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
fc := &graphql.FieldContext{
|
||||||
|
Object: "SiteInfo",
|
||||||
|
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.SiteInfo().FaceDetectionEnabled(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.(bool)
|
||||||
|
fc.Result = res
|
||||||
|
return ec.marshalNBoolean2bool(ctx, field.Selections, res)
|
||||||
|
}
|
||||||
|
|
||||||
func (ec *executionContext) _SiteInfo_periodicScanInterval(ctx context.Context, field graphql.CollectedField, obj *models.SiteInfo) (ret graphql.Marshaler) {
|
func (ec *executionContext) _SiteInfo_periodicScanInterval(ctx context.Context, field graphql.CollectedField, obj *models.SiteInfo) (ret graphql.Marshaler) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
|
@ -11133,17 +11183,31 @@ func (ec *executionContext) _SiteInfo(ctx context.Context, sel ast.SelectionSet,
|
||||||
case "initialSetup":
|
case "initialSetup":
|
||||||
out.Values[i] = ec._SiteInfo_initialSetup(ctx, field, obj)
|
out.Values[i] = ec._SiteInfo_initialSetup(ctx, field, obj)
|
||||||
if out.Values[i] == graphql.Null {
|
if out.Values[i] == graphql.Null {
|
||||||
invalids++
|
atomic.AddUint32(&invalids, 1)
|
||||||
}
|
}
|
||||||
|
case "faceDetectionEnabled":
|
||||||
|
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._SiteInfo_faceDetectionEnabled(ctx, field, obj)
|
||||||
|
if res == graphql.Null {
|
||||||
|
atomic.AddUint32(&invalids, 1)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
})
|
||||||
case "periodicScanInterval":
|
case "periodicScanInterval":
|
||||||
out.Values[i] = ec._SiteInfo_periodicScanInterval(ctx, field, obj)
|
out.Values[i] = ec._SiteInfo_periodicScanInterval(ctx, field, obj)
|
||||||
if out.Values[i] == graphql.Null {
|
if out.Values[i] == graphql.Null {
|
||||||
invalids++
|
atomic.AddUint32(&invalids, 1)
|
||||||
}
|
}
|
||||||
case "concurrentWorkers":
|
case "concurrentWorkers":
|
||||||
out.Values[i] = ec._SiteInfo_concurrentWorkers(ctx, field, obj)
|
out.Values[i] = ec._SiteInfo_concurrentWorkers(ctx, field, obj)
|
||||||
if out.Values[i] == graphql.Null {
|
if out.Values[i] == graphql.Null {
|
||||||
invalids++
|
atomic.AddUint32(&invalids, 1)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
panic("unknown field " + strconv.Quote(field.Name))
|
panic("unknown field " + strconv.Quote(field.Name))
|
||||||
|
|
|
@ -32,6 +32,10 @@ func (r imageFaceResolver) FaceGroup(ctx context.Context, obj *models.ImageFace)
|
||||||
return obj.FaceGroup, nil
|
return obj.FaceGroup, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if face_detection.GlobalFaceDetector == nil {
|
||||||
|
return nil, errors.New("face detector not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
var faceGroup models.FaceGroup
|
var faceGroup models.FaceGroup
|
||||||
if err := r.Database.Model(&obj).Association("FaceGroup").Find(&faceGroup); err != nil {
|
if err := r.Database.Model(&obj).Association("FaceGroup").Find(&faceGroup); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -48,6 +52,10 @@ func (r faceGroupResolver) ImageFaces(ctx context.Context, obj *models.FaceGroup
|
||||||
return nil, errors.New("unauthorized")
|
return nil, errors.New("unauthorized")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if face_detection.GlobalFaceDetector == nil {
|
||||||
|
return nil, errors.New("face detector not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
if err := user.FillAlbums(r.Database); err != nil {
|
if err := user.FillAlbums(r.Database); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -78,6 +86,10 @@ func (r faceGroupResolver) ImageFaceCount(ctx context.Context, obj *models.FaceG
|
||||||
return -1, errors.New("unauthorized")
|
return -1, errors.New("unauthorized")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if face_detection.GlobalFaceDetector == nil {
|
||||||
|
return -1, errors.New("face detector not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
if err := user.FillAlbums(r.Database); err != nil {
|
if err := user.FillAlbums(r.Database); err != nil {
|
||||||
return -1, err
|
return -1, err
|
||||||
}
|
}
|
||||||
|
@ -107,6 +119,10 @@ func (r *queryResolver) FaceGroup(ctx context.Context, id int) (*models.FaceGrou
|
||||||
return nil, errors.New("unauthorized")
|
return nil, errors.New("unauthorized")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if face_detection.GlobalFaceDetector == nil {
|
||||||
|
return nil, errors.New("face detector not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
if err := user.FillAlbums(r.Database); err != nil {
|
if err := user.FillAlbums(r.Database); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -135,6 +151,10 @@ func (r *queryResolver) MyFaceGroups(ctx context.Context, paginate *models.Pagin
|
||||||
return nil, errors.New("unauthorized")
|
return nil, errors.New("unauthorized")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if face_detection.GlobalFaceDetector == nil {
|
||||||
|
return nil, errors.New("face detector not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
if err := user.FillAlbums(r.Database); err != nil {
|
if err := user.FillAlbums(r.Database); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -168,6 +188,10 @@ func (r *mutationResolver) SetFaceGroupLabel(ctx context.Context, faceGroupID in
|
||||||
return nil, errors.New("unauthorized")
|
return nil, errors.New("unauthorized")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if face_detection.GlobalFaceDetector == nil {
|
||||||
|
return nil, errors.New("face detector not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
faceGroup, err := userOwnedFaceGroup(r.Database, user, faceGroupID)
|
faceGroup, err := userOwnedFaceGroup(r.Database, user, faceGroupID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -186,6 +210,10 @@ func (r *mutationResolver) CombineFaceGroups(ctx context.Context, destinationFac
|
||||||
return nil, errors.New("unauthorized")
|
return nil, errors.New("unauthorized")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if face_detection.GlobalFaceDetector == nil {
|
||||||
|
return nil, errors.New("face detector not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
destinationFaceGroup, err := userOwnedFaceGroup(r.Database, user, destinationFaceGroupID)
|
destinationFaceGroup, err := userOwnedFaceGroup(r.Database, user, destinationFaceGroupID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -223,6 +251,10 @@ func (r *mutationResolver) MoveImageFaces(ctx context.Context, imageFaceIDs []in
|
||||||
return nil, errors.New("unauthorized")
|
return nil, errors.New("unauthorized")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if face_detection.GlobalFaceDetector == nil {
|
||||||
|
return nil, errors.New("face detector not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
userOwnedImageFaceIDs := make([]int, 0)
|
userOwnedImageFaceIDs := make([]int, 0)
|
||||||
var destFaceGroup *models.FaceGroup
|
var destFaceGroup *models.FaceGroup
|
||||||
|
|
||||||
|
@ -290,6 +322,10 @@ func (r *mutationResolver) RecognizeUnlabeledFaces(ctx context.Context) ([]*mode
|
||||||
return nil, errors.New("unauthorized")
|
return nil, errors.New("unauthorized")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if face_detection.GlobalFaceDetector == nil {
|
||||||
|
return nil, errors.New("face detector not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
var updatedImageFaces []*models.ImageFace
|
var updatedImageFaces []*models.ImageFace
|
||||||
|
|
||||||
transactionError := r.Database.Transaction(func(tx *gorm.DB) error {
|
transactionError := r.Database.Transaction(func(tx *gorm.DB) error {
|
||||||
|
@ -312,6 +348,10 @@ func (r *mutationResolver) DetachImageFaces(ctx context.Context, imageFaceIDs []
|
||||||
return nil, errors.New("unauthorized")
|
return nil, errors.New("unauthorized")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if face_detection.GlobalFaceDetector == nil {
|
||||||
|
return nil, errors.New("face detector not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
userOwnedImageFaceIDs := make([]int, 0)
|
userOwnedImageFaceIDs := make([]int, 0)
|
||||||
newFaceGroup := models.FaceGroup{}
|
newFaceGroup := models.FaceGroup{}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
api "github.com/photoview/photoview/api/graphql"
|
api "github.com/photoview/photoview/api/graphql"
|
||||||
"github.com/photoview/photoview/api/graphql/auth"
|
"github.com/photoview/photoview/api/graphql/auth"
|
||||||
"github.com/photoview/photoview/api/graphql/models"
|
"github.com/photoview/photoview/api/graphql/models"
|
||||||
|
"github.com/photoview/photoview/api/scanner/face_detection"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"gorm.io/gorm/clause"
|
"gorm.io/gorm/clause"
|
||||||
)
|
)
|
||||||
|
@ -228,6 +229,10 @@ func (r *mutationResolver) FavoriteMedia(ctx context.Context, mediaID int, favor
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *mediaResolver) Faces(ctx context.Context, media *models.Media) ([]*models.ImageFace, error) {
|
func (r *mediaResolver) Faces(ctx context.Context, media *models.Media) ([]*models.ImageFace, error) {
|
||||||
|
if face_detection.GlobalFaceDetector == nil {
|
||||||
|
return []*models.ImageFace{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
if media.Faces != nil {
|
if media.Faces != nil {
|
||||||
return media.Faces, nil
|
return media.Faces, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
package resolvers
|
package resolvers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
|
|
||||||
api "github.com/photoview/photoview/api/graphql"
|
api "github.com/photoview/photoview/api/graphql"
|
||||||
"github.com/photoview/photoview/api/graphql/models"
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -35,7 +32,3 @@ type queryResolver struct{ *Resolver }
|
||||||
type subscriptionResolver struct {
|
type subscriptionResolver struct {
|
||||||
Resolver *Resolver
|
Resolver *Resolver
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *queryResolver) SiteInfo(ctx context.Context) (*models.SiteInfo, error) {
|
|
||||||
return models.GetSiteInfo(r.Database)
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
package resolvers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
api "github.com/photoview/photoview/api/graphql"
|
||||||
|
"github.com/photoview/photoview/api/graphql/models"
|
||||||
|
"github.com/photoview/photoview/api/scanner/face_detection"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *queryResolver) SiteInfo(ctx context.Context) (*models.SiteInfo, error) {
|
||||||
|
return models.GetSiteInfo(r.Database)
|
||||||
|
}
|
||||||
|
|
||||||
|
type SiteInfoResolver struct {
|
||||||
|
*Resolver
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Resolver) SiteInfo() api.SiteInfoResolver {
|
||||||
|
return &SiteInfoResolver{r}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (SiteInfoResolver) FaceDetectionEnabled(ctx context.Context, obj *models.SiteInfo) (bool, error) {
|
||||||
|
return face_detection.GlobalFaceDetector != nil, nil
|
||||||
|
}
|
|
@ -343,10 +343,12 @@ func (r *mutationResolver) UserRemoveRootAlbum(ctx context.Context, userID int,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reload faces as media might have been deleted
|
// Reload faces as media might have been deleted
|
||||||
|
if face_detection.GlobalFaceDetector == nil {
|
||||||
if err := face_detection.GlobalFaceDetector.ReloadFacesFromDatabase(r.Database); err != nil {
|
if err := face_detection.GlobalFaceDetector.ReloadFacesFromDatabase(r.Database); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return &album, nil
|
return &album, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,7 +68,7 @@ type Query {
|
||||||
"Get media owned by the logged in user, returned in GeoJson format"
|
"Get media owned by the logged in user, returned in GeoJson format"
|
||||||
myMediaGeoJson: Any! @isAuthorized
|
myMediaGeoJson: Any! @isAuthorized
|
||||||
"Get the mapbox api token, returns null if mapbox is not enabled"
|
"Get the mapbox api token, returns null if mapbox is not enabled"
|
||||||
mapboxToken: String
|
mapboxToken: String @isAuthorized
|
||||||
|
|
||||||
shareToken(credentials: ShareTokenCredentials!): ShareToken!
|
shareToken(credentials: ShareTokenCredentials!): ShareToken!
|
||||||
shareTokenValidatePassword(credentials: ShareTokenCredentials!): Boolean!
|
shareTokenValidatePassword(credentials: ShareTokenCredentials!): Boolean!
|
||||||
|
@ -201,7 +201,10 @@ type ShareToken {
|
||||||
|
|
||||||
"General information about the site"
|
"General information about the site"
|
||||||
type SiteInfo {
|
type SiteInfo {
|
||||||
|
"Whether or not the initial setup wizard should be shown"
|
||||||
initialSetup: Boolean!
|
initialSetup: Boolean!
|
||||||
|
"Whether or not face detection is enabled and working"
|
||||||
|
faceDetectionEnabled: Boolean!
|
||||||
"How often automatic scans should be initiated in seconds"
|
"How often automatic scans should be initiated in seconds"
|
||||||
periodicScanInterval: Int! @isAdmin
|
periodicScanInterval: Int! @isAdmin
|
||||||
"How many max concurrent scanner jobs that should run at once"
|
"How many max concurrent scanner jobs that should run at once"
|
||||||
|
|
|
@ -53,10 +53,12 @@ func CleanupMedia(db *gorm.DB, albumId int, albumMedia []*models.Media) []error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reload faces after deleting media
|
// Reload faces after deleting media
|
||||||
|
if face_detection.GlobalFaceDetector != nil {
|
||||||
if err := face_detection.GlobalFaceDetector.ReloadFacesFromDatabase(db); err != nil {
|
if err := face_detection.GlobalFaceDetector.ReloadFacesFromDatabase(db); err != nil {
|
||||||
deleteErrors = append(deleteErrors, errors.Wrap(err, "reload faces from database"))
|
deleteErrors = append(deleteErrors, errors.Wrap(err, "reload faces from database"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return deleteErrors
|
return deleteErrors
|
||||||
}
|
}
|
||||||
|
@ -124,9 +126,11 @@ func deleteOldUserAlbums(db *gorm.DB, scannedAlbums []*models.Album, user *model
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reload faces after deleting albums
|
// Reload faces after deleting albums
|
||||||
|
if face_detection.GlobalFaceDetector == nil {
|
||||||
if err := face_detection.GlobalFaceDetector.ReloadFacesFromDatabase(db); err != nil {
|
if err := face_detection.GlobalFaceDetector.ReloadFacesFromDatabase(db); err != nil {
|
||||||
deleteErrors = append(deleteErrors, err)
|
deleteErrors = append(deleteErrors, err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return deleteErrors
|
return deleteErrors
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,9 +19,13 @@ type FaceDetector struct {
|
||||||
imageFaceIDs []int
|
imageFaceIDs []int
|
||||||
}
|
}
|
||||||
|
|
||||||
var GlobalFaceDetector FaceDetector
|
var GlobalFaceDetector *FaceDetector = nil
|
||||||
|
|
||||||
func InitializeFaceDetector(db *gorm.DB) error {
|
func InitializeFaceDetector(db *gorm.DB) error {
|
||||||
|
if utils.EnvDisableFaceRecognition.GetBool() {
|
||||||
|
log.Printf("Face detection disabled (%s=1)\n", utils.EnvDisableFaceRecognition.GetName())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
log.Println("Initializing face detector")
|
log.Println("Initializing face detector")
|
||||||
|
|
||||||
|
@ -35,7 +39,7 @@ func InitializeFaceDetector(db *gorm.DB) error {
|
||||||
return errors.Wrap(err, "get face detection samples from database")
|
return errors.Wrap(err, "get face detection samples from database")
|
||||||
}
|
}
|
||||||
|
|
||||||
GlobalFaceDetector = FaceDetector{
|
GlobalFaceDetector = &FaceDetector{
|
||||||
rec: rec,
|
rec: rec,
|
||||||
faceDescriptors: faceDescriptors,
|
faceDescriptors: faceDescriptors,
|
||||||
faceGroupIDs: faceGroupIDs,
|
faceGroupIDs: faceGroupIDs,
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/photoview/photoview/api/utils"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"gopkg.in/vansante/go-ffprobe.v2"
|
"gopkg.in/vansante/go-ffprobe.v2"
|
||||||
)
|
)
|
||||||
|
@ -33,6 +34,11 @@ type FfmpegWorker struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDarktableWorker() *DarktableWorker {
|
func newDarktableWorker() *DarktableWorker {
|
||||||
|
if utils.EnvDisableRawProcessing.GetBool() {
|
||||||
|
log.Printf("Executable worker disabled (%s=1): darktable\n", utils.EnvDisableRawProcessing.GetName())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
path, err := exec.LookPath("darktable-cli")
|
path, err := exec.LookPath("darktable-cli")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Executable worker not found: darktable")
|
log.Println("Executable worker not found: darktable")
|
||||||
|
@ -54,6 +60,11 @@ func newDarktableWorker() *DarktableWorker {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newFfmpegWorker() *FfmpegWorker {
|
func newFfmpegWorker() *FfmpegWorker {
|
||||||
|
if utils.EnvDisableVideoEncoding.GetBool() {
|
||||||
|
log.Printf("Executable worker disabled (%s=1): ffmpeg\n", utils.EnvDisableVideoEncoding.GetName())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
path, err := exec.LookPath("ffmpeg")
|
path, err := exec.LookPath("ffmpeg")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Executable worker not found: ffmpeg")
|
log.Println("Executable worker not found: ffmpeg")
|
||||||
|
|
|
@ -141,6 +141,9 @@ func scanAlbum(album *models.Album, cache *scanner_cache.AlbumScannerCache, db *
|
||||||
|
|
||||||
if processing_was_needed && media.Type == models.MediaTypePhoto {
|
if processing_was_needed && media.Type == models.MediaTypePhoto {
|
||||||
go func(media *models.Media) {
|
go func(media *models.Media) {
|
||||||
|
if face_detection.GlobalFaceDetector == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
if err := face_detection.GlobalFaceDetector.DetectFaces(db, media); err != nil {
|
if err := face_detection.GlobalFaceDetector.DetectFaces(db, media); err != nil {
|
||||||
scanner_utils.ScannerError("Error detecting faces in image (%s): %s", media.Path, err)
|
scanner_utils.ScannerError("Error detecting faces in image (%s): %s", media.Path, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package utils
|
package utils
|
||||||
|
|
||||||
import "os"
|
import (
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
// EnvironmentVariable represents the name of an environment variable used to configure Photoview
|
// EnvironmentVariable represents the name of an environment variable used to configure Photoview
|
||||||
type EnvironmentVariable string
|
type EnvironmentVariable string
|
||||||
|
@ -30,6 +33,13 @@ const (
|
||||||
EnvSqlitePath EnvironmentVariable = "PHOTOVIEW_SQLITE_PATH"
|
EnvSqlitePath EnvironmentVariable = "PHOTOVIEW_SQLITE_PATH"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Feature related
|
||||||
|
const (
|
||||||
|
EnvDisableFaceRecognition EnvironmentVariable = "PHOTOVIEW_DISABLE_FACE_RECOGNITION"
|
||||||
|
EnvDisableVideoEncoding EnvironmentVariable = "PHOTOVIEW_DISABLE_VIDEO_ENCODING"
|
||||||
|
EnvDisableRawProcessing EnvironmentVariable = "PHOTOVIEW_DISABLE_RAW_PROCESSING"
|
||||||
|
)
|
||||||
|
|
||||||
// GetName returns the name of the environment variable itself
|
// GetName returns the name of the environment variable itself
|
||||||
func (v EnvironmentVariable) GetName() string {
|
func (v EnvironmentVariable) GetName() string {
|
||||||
return string(v)
|
return string(v)
|
||||||
|
@ -40,6 +50,20 @@ func (v EnvironmentVariable) GetValue() string {
|
||||||
return os.Getenv(string(v))
|
return os.Getenv(string(v))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetBool returns the environment variable as a boolean (defaults to false if not defined)
|
||||||
|
func (v EnvironmentVariable) GetBool() bool {
|
||||||
|
value := strings.ToLower(os.Getenv(string(v)))
|
||||||
|
trueValues := []string{"1", "true"}
|
||||||
|
|
||||||
|
for _, x := range trueValues {
|
||||||
|
if value == x {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// ShouldServeUI whether or not the "serve ui" option is enabled
|
// ShouldServeUI whether or not the "serve ui" option is enabled
|
||||||
func ShouldServeUI() bool {
|
func ShouldServeUI() bool {
|
||||||
return EnvServeUI.GetValue() == "1"
|
return EnvServeUI.GetValue() == "1"
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { NavLink } from 'react-router-dom'
|
||||||
import { useQuery, gql } from '@apollo/client'
|
import { useQuery, gql } from '@apollo/client'
|
||||||
import { authToken } from '../../helpers/authentication'
|
import { authToken } from '../../helpers/authentication'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { mapboxEnabledQuery } from '../../__generated__/mapboxEnabledQuery'
|
||||||
|
|
||||||
export const MAPBOX_QUERY = gql`
|
export const MAPBOX_QUERY = gql`
|
||||||
query mapboxEnabledQuery {
|
query mapboxEnabledQuery {
|
||||||
|
@ -10,6 +11,14 @@ export const MAPBOX_QUERY = gql`
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
export const FACE_DETECTION_ENABLED_QUERY = gql`
|
||||||
|
query faceDetectionEnabled {
|
||||||
|
siteInfo {
|
||||||
|
faceDetectionEnabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
type MenuButtonProps = {
|
type MenuButtonProps = {
|
||||||
to: string
|
to: string
|
||||||
exact: boolean
|
exact: boolean
|
||||||
|
@ -56,9 +65,16 @@ const MenuSeparator = () => (
|
||||||
export const MainMenu = () => {
|
export const MainMenu = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const mapboxQuery = authToken() ? useQuery(MAPBOX_QUERY) : null
|
const mapboxQuery = authToken()
|
||||||
|
? useQuery<mapboxEnabledQuery>(MAPBOX_QUERY)
|
||||||
|
: null
|
||||||
|
const faceDetectionEnabledQuery = authToken()
|
||||||
|
? useQuery(FACE_DETECTION_ENABLED_QUERY)
|
||||||
|
: null
|
||||||
|
|
||||||
const mapboxEnabled = !!mapboxQuery?.data?.mapboxToken
|
const mapboxEnabled = !!mapboxQuery?.data?.mapboxToken
|
||||||
|
const faceDetectionEnabled =
|
||||||
|
!!faceDetectionEnabledQuery?.data?.siteInfo?.faceDetectionEnabled
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed w-full bottom-0 lg:bottom-auto lg:top-[84px] z-30 bg-white shadow-separator lg:shadow-none lg:w-[240px] lg:ml-8 lg:mr-5 flex-shrink-0">
|
<div className="fixed w-full bottom-0 lg:bottom-auto lg:top-[84px] z-30 bg-white shadow-separator lg:shadow-none lg:w-[240px] lg:ml-8 lg:mr-5 flex-shrink-0">
|
||||||
|
@ -104,6 +120,7 @@ export const MainMenu = () => {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
{faceDetectionEnabled ? (
|
||||||
<MenuButton
|
<MenuButton
|
||||||
to="/people"
|
to="/people"
|
||||||
exact
|
exact
|
||||||
|
@ -117,6 +134,7 @@ export const MainMenu = () => {
|
||||||
</svg>
|
</svg>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
) : null}
|
||||||
<MenuSeparator />
|
<MenuSeparator />
|
||||||
<MenuButton
|
<MenuButton
|
||||||
to="/settings"
|
to="/settings"
|
||||||
|
|
|
@ -330,18 +330,6 @@ const flashLookup = (t: TranslationFn): { [key: number]: string } => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// From https://exiftool.org/TagNames/EXIF.html
|
|
||||||
// const orientation = {
|
|
||||||
// 1: 'Horizontal (normal)',
|
|
||||||
// 2: 'Mirror horizontal',
|
|
||||||
// 3: 'Rotate 180',
|
|
||||||
// 4: 'Mirror vertical',
|
|
||||||
// 5: 'Mirror horizontal and rotate 270 CW',
|
|
||||||
// 6: 'Rotate 90 CW',
|
|
||||||
// 7: 'Mirror horizontal and rotate 90 CW',
|
|
||||||
// 8: 'Rotate 270 CW',
|
|
||||||
// }
|
|
||||||
|
|
||||||
type SidebarContentProps = {
|
type SidebarContentProps = {
|
||||||
media: MediaSidebarMedia
|
media: MediaSidebarMedia
|
||||||
hidePreview?: boolean
|
hidePreview?: boolean
|
||||||
|
|
|
@ -107,7 +107,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"photos_page": {
|
"photos_page": {
|
||||||
"title": "Billeder"
|
"title": "Tidslinje"
|
||||||
},
|
},
|
||||||
"places_page": {
|
"places_page": {
|
||||||
"title": "Kort"
|
"title": "Kort"
|
||||||
|
@ -285,13 +285,13 @@
|
||||||
"sidemenu": {
|
"sidemenu": {
|
||||||
"albums": "Albums",
|
"albums": "Albums",
|
||||||
"people": "Personer",
|
"people": "Personer",
|
||||||
"photos": "Billeder",
|
"photos": "Tidslinje",
|
||||||
"places": "Kort",
|
"places": "Kort",
|
||||||
"settings": "Indstillinger"
|
"settings": "Indstillinger"
|
||||||
},
|
},
|
||||||
"title": {
|
"title": {
|
||||||
"loading_album": "Loader album",
|
"loading_album": "Loader album",
|
||||||
"login": "Logind",
|
"login": "Log ind",
|
||||||
"people": "Personer",
|
"people": "Personer",
|
||||||
"settings": "Indstillinger"
|
"settings": "Indstillinger"
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,7 +107,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"photos_page": {
|
"photos_page": {
|
||||||
"title": "Photos"
|
"title": "Timeline"
|
||||||
},
|
},
|
||||||
"places_page": {
|
"places_page": {
|
||||||
"title": "Places"
|
"title": "Places"
|
||||||
|
@ -285,7 +285,7 @@
|
||||||
"sidemenu": {
|
"sidemenu": {
|
||||||
"albums": "Albums",
|
"albums": "Albums",
|
||||||
"people": "People",
|
"people": "People",
|
||||||
"photos": "Photos",
|
"photos": "Timeline",
|
||||||
"places": "Places",
|
"places": "Places",
|
||||||
"settings": "Settings"
|
"settings": "Settings"
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue