Introduce thumbnail filtering options.
This commit is contained in:
parent
446ef9a464
commit
5c4eeef870
|
@ -151,30 +151,31 @@ type ComplexityRoot struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
Mutation struct {
|
Mutation struct {
|
||||||
AuthorizeUser func(childComplexity int, username string, password string) int
|
AuthorizeUser func(childComplexity int, username string, password string) int
|
||||||
ChangeUserPreferences func(childComplexity int, language *string) int
|
ChangeUserPreferences func(childComplexity int, language *string) int
|
||||||
CombineFaceGroups func(childComplexity int, destinationFaceGroupID int, sourceFaceGroupID int) int
|
CombineFaceGroups func(childComplexity int, destinationFaceGroupID int, sourceFaceGroupID int) int
|
||||||
CreateUser func(childComplexity int, username string, password *string, admin bool) int
|
CreateUser func(childComplexity int, username string, password *string, admin bool) int
|
||||||
DeleteShareToken func(childComplexity int, token string) int
|
DeleteShareToken func(childComplexity int, token string) int
|
||||||
DeleteUser func(childComplexity int, id int) int
|
DeleteUser func(childComplexity int, id int) int
|
||||||
DetachImageFaces func(childComplexity int, imageFaceIDs []int) int
|
DetachImageFaces func(childComplexity int, imageFaceIDs []int) int
|
||||||
FavoriteMedia func(childComplexity int, mediaID int, favorite bool) int
|
FavoriteMedia func(childComplexity int, mediaID int, favorite bool) int
|
||||||
InitialSetupWizard func(childComplexity int, username string, password string, rootPath string) int
|
InitialSetupWizard func(childComplexity int, username string, password string, rootPath string) int
|
||||||
MoveImageFaces func(childComplexity int, imageFaceIDs []int, destinationFaceGroupID int) int
|
MoveImageFaces func(childComplexity int, imageFaceIDs []int, destinationFaceGroupID int) int
|
||||||
ProtectShareToken func(childComplexity int, token string, password *string) int
|
ProtectShareToken func(childComplexity int, token string, password *string) int
|
||||||
RecognizeUnlabeledFaces func(childComplexity int) int
|
RecognizeUnlabeledFaces func(childComplexity int) int
|
||||||
ResetAlbumCover func(childComplexity int, albumID int) int
|
ResetAlbumCover func(childComplexity int, albumID int) int
|
||||||
ScanAll func(childComplexity int) int
|
ScanAll func(childComplexity int) int
|
||||||
ScanUser func(childComplexity int, userID int) int
|
ScanUser func(childComplexity int, userID int) int
|
||||||
SetAlbumCover func(childComplexity int, coverID int) int
|
SetAlbumCover func(childComplexity int, coverID int) int
|
||||||
SetFaceGroupLabel func(childComplexity int, faceGroupID int, label *string) int
|
SetFaceGroupLabel func(childComplexity int, faceGroupID int, label *string) int
|
||||||
SetPeriodicScanInterval func(childComplexity int, interval int) int
|
SetPeriodicScanInterval func(childComplexity int, interval int) int
|
||||||
SetScannerConcurrentWorkers func(childComplexity int, workers int) int
|
SetScannerConcurrentWorkers func(childComplexity int, workers int) int
|
||||||
ShareAlbum func(childComplexity int, albumID int, expire *time.Time, password *string) int
|
SetThumbnailDownsampleMethod func(childComplexity int, method int) int
|
||||||
ShareMedia func(childComplexity int, mediaID int, expire *time.Time, password *string) int
|
ShareAlbum func(childComplexity int, albumID int, expire *time.Time, password *string) int
|
||||||
UpdateUser func(childComplexity int, id int, username *string, password *string, admin *bool) int
|
ShareMedia func(childComplexity int, mediaID int, expire *time.Time, password *string) int
|
||||||
UserAddRootPath func(childComplexity int, id int, rootPath string) int
|
UpdateUser func(childComplexity int, id int, username *string, password *string, admin *bool) int
|
||||||
UserRemoveRootAlbum func(childComplexity int, userID int, albumID int) int
|
UserAddRootPath func(childComplexity int, id int, rootPath string) int
|
||||||
|
UserRemoveRootAlbum func(childComplexity int, userID int, albumID int) int
|
||||||
}
|
}
|
||||||
|
|
||||||
Notification struct {
|
Notification struct {
|
||||||
|
@ -236,6 +237,7 @@ type ComplexityRoot struct {
|
||||||
FaceDetectionEnabled 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
|
||||||
|
ThumbnailMethod func(childComplexity int) int
|
||||||
}
|
}
|
||||||
|
|
||||||
Subscription struct {
|
Subscription struct {
|
||||||
|
@ -326,6 +328,7 @@ type MutationResolver interface {
|
||||||
UserRemoveRootAlbum(ctx context.Context, userID int, albumID int) (*models.Album, error)
|
UserRemoveRootAlbum(ctx context.Context, userID int, albumID int) (*models.Album, error)
|
||||||
SetPeriodicScanInterval(ctx context.Context, interval int) (int, error)
|
SetPeriodicScanInterval(ctx context.Context, interval int) (int, error)
|
||||||
SetScannerConcurrentWorkers(ctx context.Context, workers int) (int, error)
|
SetScannerConcurrentWorkers(ctx context.Context, workers int) (int, error)
|
||||||
|
SetThumbnailDownsampleMethod(ctx context.Context, method int) (int, error)
|
||||||
ChangeUserPreferences(ctx context.Context, language *string) (*models.UserPreferences, error)
|
ChangeUserPreferences(ctx context.Context, language *string) (*models.UserPreferences, error)
|
||||||
ResetAlbumCover(ctx context.Context, albumID int) (*models.Album, error)
|
ResetAlbumCover(ctx context.Context, albumID int) (*models.Album, error)
|
||||||
SetAlbumCover(ctx context.Context, coverID int) (*models.Album, error)
|
SetAlbumCover(ctx context.Context, coverID int) (*models.Album, error)
|
||||||
|
@ -1057,6 +1060,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
||||||
|
|
||||||
return e.complexity.Mutation.SetScannerConcurrentWorkers(childComplexity, args["workers"].(int)), true
|
return e.complexity.Mutation.SetScannerConcurrentWorkers(childComplexity, args["workers"].(int)), true
|
||||||
|
|
||||||
|
case "Mutation.setThumbnailDownsampleMethod":
|
||||||
|
if e.complexity.Mutation.SetThumbnailDownsampleMethod == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
args, err := ec.field_Mutation_setThumbnailDownsampleMethod_args(context.TODO(), rawArgs)
|
||||||
|
if err != nil {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.complexity.Mutation.SetThumbnailDownsampleMethod(childComplexity, args["method"].(int)), true
|
||||||
|
|
||||||
case "Mutation.shareAlbum":
|
case "Mutation.shareAlbum":
|
||||||
if e.complexity.Mutation.ShareAlbum == nil {
|
if e.complexity.Mutation.ShareAlbum == nil {
|
||||||
break
|
break
|
||||||
|
@ -1478,6 +1493,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
||||||
|
|
||||||
return e.complexity.SiteInfo.PeriodicScanInterval(childComplexity), true
|
return e.complexity.SiteInfo.PeriodicScanInterval(childComplexity), true
|
||||||
|
|
||||||
|
case "SiteInfo.thumbnailMethod":
|
||||||
|
if e.complexity.SiteInfo.ThumbnailMethod == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.complexity.SiteInfo.ThumbnailMethod(childComplexity), true
|
||||||
|
|
||||||
case "Subscription.notification":
|
case "Subscription.notification":
|
||||||
if e.complexity.Subscription.Notification == nil {
|
if e.complexity.Subscription.Notification == nil {
|
||||||
break
|
break
|
||||||
|
@ -2156,6 +2178,21 @@ func (ec *executionContext) field_Mutation_setScannerConcurrentWorkers_args(ctx
|
||||||
return args, nil
|
return args, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) field_Mutation_setThumbnailDownsampleMethod_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
|
||||||
|
var err error
|
||||||
|
args := map[string]interface{}{}
|
||||||
|
var arg0 int
|
||||||
|
if tmp, ok := rawArgs["method"]; ok {
|
||||||
|
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("method"))
|
||||||
|
arg0, err = ec.unmarshalNInt2int(ctx, tmp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
args["method"] = arg0
|
||||||
|
return args, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (ec *executionContext) field_Mutation_shareAlbum_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
|
func (ec *executionContext) field_Mutation_shareAlbum_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
|
||||||
var err error
|
var err error
|
||||||
args := map[string]interface{}{}
|
args := map[string]interface{}{}
|
||||||
|
@ -7158,6 +7195,81 @@ func (ec *executionContext) fieldContext_Mutation_setScannerConcurrentWorkers(ct
|
||||||
return fc, nil
|
return fc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) _Mutation_setThumbnailDownsampleMethod(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
||||||
|
fc, err := ec.fieldContext_Mutation_setThumbnailDownsampleMethod(ctx, field)
|
||||||
|
if err != nil {
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
ctx = graphql.WithFieldContext(ctx, fc)
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
ec.Error(ctx, ec.Recover(ctx, r))
|
||||||
|
ret = graphql.Null
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||||
|
directive0 := func(rctx context.Context) (interface{}, error) {
|
||||||
|
ctx = rctx // use context from middleware stack in children
|
||||||
|
return ec.resolvers.Mutation().SetThumbnailDownsampleMethod(rctx, fc.Args["method"].(int))
|
||||||
|
}
|
||||||
|
directive1 := func(ctx context.Context) (interface{}, error) {
|
||||||
|
if ec.directives.IsAdmin == nil {
|
||||||
|
return nil, errors.New("directive isAdmin is not implemented")
|
||||||
|
}
|
||||||
|
return ec.directives.IsAdmin(ctx, nil, directive0)
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp, err := directive1(rctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, graphql.ErrorOnPath(ctx, err)
|
||||||
|
}
|
||||||
|
if tmp == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if data, ok := tmp.(int); ok {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf(`unexpected type %T from directive, should be int`, tmp)
|
||||||
|
})
|
||||||
|
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.(int)
|
||||||
|
fc.Result = res
|
||||||
|
return ec.marshalNInt2int(ctx, field.Selections, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) fieldContext_Mutation_setThumbnailDownsampleMethod(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||||
|
fc = &graphql.FieldContext{
|
||||||
|
Object: "Mutation",
|
||||||
|
Field: field,
|
||||||
|
IsMethod: true,
|
||||||
|
IsResolver: true,
|
||||||
|
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||||
|
return nil, errors.New("field of type Int does not have child fields")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
err = ec.Recover(ctx, r)
|
||||||
|
ec.Error(ctx, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
ctx = graphql.WithFieldContext(ctx, fc)
|
||||||
|
if fc.Args, err = ec.field_Mutation_setThumbnailDownsampleMethod_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
|
||||||
|
ec.Error(ctx, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return fc, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (ec *executionContext) _Mutation_changeUserPreferences(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
func (ec *executionContext) _Mutation_changeUserPreferences(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
||||||
fc, err := ec.fieldContext_Mutation_changeUserPreferences(ctx, field)
|
fc, err := ec.fieldContext_Mutation_changeUserPreferences(ctx, field)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -8240,6 +8352,8 @@ func (ec *executionContext) fieldContext_Query_siteInfo(ctx context.Context, fie
|
||||||
return ec.fieldContext_SiteInfo_periodicScanInterval(ctx, field)
|
return ec.fieldContext_SiteInfo_periodicScanInterval(ctx, field)
|
||||||
case "concurrentWorkers":
|
case "concurrentWorkers":
|
||||||
return ec.fieldContext_SiteInfo_concurrentWorkers(ctx, field)
|
return ec.fieldContext_SiteInfo_concurrentWorkers(ctx, field)
|
||||||
|
case "thumbnailMethod":
|
||||||
|
return ec.fieldContext_SiteInfo_thumbnailMethod(ctx, field)
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("no field named %q was found under type SiteInfo", field.Name)
|
return nil, fmt.Errorf("no field named %q was found under type SiteInfo", field.Name)
|
||||||
},
|
},
|
||||||
|
@ -10584,6 +10698,70 @@ func (ec *executionContext) fieldContext_SiteInfo_concurrentWorkers(ctx context.
|
||||||
return fc, nil
|
return fc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) _SiteInfo_thumbnailMethod(ctx context.Context, field graphql.CollectedField, obj *models.SiteInfo) (ret graphql.Marshaler) {
|
||||||
|
fc, err := ec.fieldContext_SiteInfo_thumbnailMethod(ctx, field)
|
||||||
|
if err != nil {
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
ctx = graphql.WithFieldContext(ctx, fc)
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
ec.Error(ctx, ec.Recover(ctx, r))
|
||||||
|
ret = graphql.Null
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||||
|
directive0 := func(rctx context.Context) (interface{}, error) {
|
||||||
|
ctx = rctx // use context from middleware stack in children
|
||||||
|
return obj.ThumbnailMethod, nil
|
||||||
|
}
|
||||||
|
directive1 := func(ctx context.Context) (interface{}, error) {
|
||||||
|
if ec.directives.IsAdmin == nil {
|
||||||
|
return nil, errors.New("directive isAdmin is not implemented")
|
||||||
|
}
|
||||||
|
return ec.directives.IsAdmin(ctx, obj, directive0)
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp, err := directive1(rctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, graphql.ErrorOnPath(ctx, err)
|
||||||
|
}
|
||||||
|
if tmp == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if data, ok := tmp.(int); ok {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf(`unexpected type %T from directive, should be int`, tmp)
|
||||||
|
})
|
||||||
|
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.(int)
|
||||||
|
fc.Result = res
|
||||||
|
return ec.marshalNInt2int(ctx, field.Selections, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) fieldContext_SiteInfo_thumbnailMethod(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||||
|
fc = &graphql.FieldContext{
|
||||||
|
Object: "SiteInfo",
|
||||||
|
Field: field,
|
||||||
|
IsMethod: false,
|
||||||
|
IsResolver: false,
|
||||||
|
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||||
|
return nil, errors.New("field of type Int does not have child fields")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return fc, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (ec *executionContext) _Subscription_notification(ctx context.Context, field graphql.CollectedField) (ret func(ctx context.Context) graphql.Marshaler) {
|
func (ec *executionContext) _Subscription_notification(ctx context.Context, field graphql.CollectedField) (ret func(ctx context.Context) graphql.Marshaler) {
|
||||||
fc, err := ec.fieldContext_Subscription_notification(ctx, field)
|
fc, err := ec.fieldContext_Subscription_notification(ctx, field)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -14625,6 +14803,15 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
|
||||||
return ec._Mutation_setScannerConcurrentWorkers(ctx, field)
|
return ec._Mutation_setScannerConcurrentWorkers(ctx, field)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if out.Values[i] == graphql.Null {
|
||||||
|
invalids++
|
||||||
|
}
|
||||||
|
case "setThumbnailDownsampleMethod":
|
||||||
|
|
||||||
|
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
|
||||||
|
return ec._Mutation_setThumbnailDownsampleMethod(ctx, field)
|
||||||
|
})
|
||||||
|
|
||||||
if out.Values[i] == graphql.Null {
|
if out.Values[i] == graphql.Null {
|
||||||
invalids++
|
invalids++
|
||||||
}
|
}
|
||||||
|
@ -15419,6 +15606,13 @@ func (ec *executionContext) _SiteInfo(ctx context.Context, sel ast.SelectionSet,
|
||||||
|
|
||||||
out.Values[i] = ec._SiteInfo_concurrentWorkers(ctx, field, obj)
|
out.Values[i] = ec._SiteInfo_concurrentWorkers(ctx, field, obj)
|
||||||
|
|
||||||
|
if out.Values[i] == graphql.Null {
|
||||||
|
atomic.AddUint32(&invalids, 1)
|
||||||
|
}
|
||||||
|
case "thumbnailMethod":
|
||||||
|
|
||||||
|
out.Values[i] = ec._SiteInfo_thumbnailMethod(ctx, field, obj)
|
||||||
|
|
||||||
if out.Values[i] == graphql.Null {
|
if out.Values[i] == graphql.Null {
|
||||||
atomic.AddUint32(&invalids, 1)
|
atomic.AddUint32(&invalids, 1)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ type SiteInfo struct {
|
||||||
InitialSetup bool `gorm:"not null"`
|
InitialSetup bool `gorm:"not null"`
|
||||||
PeriodicScanInterval int `gorm:"not null"`
|
PeriodicScanInterval int `gorm:"not null"`
|
||||||
ConcurrentWorkers int `gorm:"not null"`
|
ConcurrentWorkers int `gorm:"not null"`
|
||||||
|
ThumbnailMethod int `gorm:"not null"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (SiteInfo) TableName() string {
|
func (SiteInfo) TableName() string {
|
||||||
|
@ -26,6 +27,7 @@ func DefaultSiteInfo(db *gorm.DB) SiteInfo {
|
||||||
InitialSetup: true,
|
InitialSetup: true,
|
||||||
PeriodicScanInterval: 0,
|
PeriodicScanInterval: 0,
|
||||||
ConcurrentWorkers: defaultConcurrentWorkers,
|
ConcurrentWorkers: defaultConcurrentWorkers,
|
||||||
|
ThumbnailMethod: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
package resolvers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/photoview/photoview/api/graphql/models"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *mutationResolver) SetThumbnailDownsampleMethod(ctx context.Context, method int) (int, error) {
|
||||||
|
db := r.DB(ctx)
|
||||||
|
|
||||||
|
if method > 5 {
|
||||||
|
return 0, errors.New("The requested filter is unsupported, defaulting to nearest neighbor")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.Session(&gorm.Session{AllowGlobalUpdate: true}).Model(&models.SiteInfo{}).Update("thumbnail_method", method).Error; err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var siteInfo models.SiteInfo
|
||||||
|
if err := db.First(&siteInfo).Error; err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanner_queue.ChangeScannerConcurrentWorkers(siteInfo.ConcurrentWorkers)
|
||||||
|
|
||||||
|
return siteInfo.ThumbnailMethod, nil
|
||||||
|
}
|
|
@ -163,6 +163,9 @@ type Mutation {
|
||||||
"Set max number of concurrent scanner jobs running at once"
|
"Set max number of concurrent scanner jobs running at once"
|
||||||
setScannerConcurrentWorkers(workers: Int!): Int! @isAdmin
|
setScannerConcurrentWorkers(workers: Int!): Int! @isAdmin
|
||||||
|
|
||||||
|
"Set the filter to be used when generating thumbnails"
|
||||||
|
setThumbnailDownsampleMethod(method: Int!): Int! @isAdmin
|
||||||
|
|
||||||
"Change user preferences for the logged in user"
|
"Change user preferences for the logged in user"
|
||||||
changeUserPreferences(language: String): UserPreferences! @isAuthorized
|
changeUserPreferences(language: String): UserPreferences! @isAuthorized
|
||||||
|
|
||||||
|
@ -257,6 +260,8 @@ type SiteInfo {
|
||||||
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"
|
||||||
concurrentWorkers: Int! @isAdmin
|
concurrentWorkers: Int! @isAdmin
|
||||||
|
"The filter to use when generating thumbnails"
|
||||||
|
thumbnailMethod: Int! @isAdmin
|
||||||
}
|
}
|
||||||
|
|
||||||
type User {
|
type User {
|
||||||
|
|
|
@ -17,9 +17,25 @@ import (
|
||||||
"gopkg.in/vansante/go-ffprobe.v2"
|
"gopkg.in/vansante/go-ffprobe.v2"
|
||||||
|
|
||||||
_ "github.com/strukturag/libheif/go/heif"
|
_ "github.com/strukturag/libheif/go/heif"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
func EncodeThumbnail(inputPath string, outputPath string) (*media_utils.PhotoDimensions, error) {
|
var thumbFilter = map[int]imaging.ResampleFilter{
|
||||||
|
0: imaging.NearestNeighbor,
|
||||||
|
1: imaging.Box,
|
||||||
|
2: imaging.Linear,
|
||||||
|
3: imaging.MitchellNetravali,
|
||||||
|
4: imaging.CatmullRom,
|
||||||
|
5: imaging.Lanczos,
|
||||||
|
}
|
||||||
|
|
||||||
|
func EncodeThumbnail(db *gorm.DB, inputPath string, outputPath string) (*media_utils.PhotoDimensions, error) {
|
||||||
|
|
||||||
|
var siteInfo models.SiteInfo
|
||||||
|
if err := db.First(&siteInfo).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
inputImage, err := imaging.Open(inputPath, imaging.AutoOrientation(true))
|
inputImage, err := imaging.Open(inputPath, imaging.AutoOrientation(true))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -29,7 +45,7 @@ func EncodeThumbnail(inputPath string, outputPath string) (*media_utils.PhotoDim
|
||||||
dimensions := media_utils.PhotoDimensionsFromRect(inputImage.Bounds())
|
dimensions := media_utils.PhotoDimensionsFromRect(inputImage.Bounds())
|
||||||
dimensions = dimensions.ThumbnailScale()
|
dimensions = dimensions.ThumbnailScale()
|
||||||
|
|
||||||
thumbImage := imaging.Resize(inputImage, dimensions.Width, dimensions.Height, imaging.NearestNeighbor)
|
thumbImage := imaging.Resize(inputImage, dimensions.Width, dimensions.Height, thumbFilter[siteInfo.ThumbnailMethod])
|
||||||
if err = encodeImageJPEG(thumbImage, outputPath, 60); err != nil {
|
if err = encodeImageJPEG(thumbImage, outputPath, 60); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -128,7 +128,7 @@ func (t ProcessPhotoTask) ProcessMedia(ctx scanner_task.TaskContext, mediaData *
|
||||||
updatedURLs = append(updatedURLs, thumbURL)
|
updatedURLs = append(updatedURLs, thumbURL)
|
||||||
fmt.Printf("Thumbnail photo found in database but not in cache, re-encoding photo to cache: %s\n", thumbURL.MediaName)
|
fmt.Printf("Thumbnail photo found in database but not in cache, re-encoding photo to cache: %s\n", thumbURL.MediaName)
|
||||||
|
|
||||||
_, err := media_encoding.EncodeThumbnail(baseImagePath, thumbPath)
|
_, err := media_encoding.EncodeThumbnail(ctx.GetDB(), baseImagePath, thumbPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []*models.MediaURL{}, errors.Wrap(err, "could not create thumbnail cached image")
|
return []*models.MediaURL{}, errors.Wrap(err, "could not create thumbnail cached image")
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,7 +59,7 @@ func generateSaveHighResJPEG(tx *gorm.DB, media *models.Media, imageData *media_
|
||||||
func generateSaveThumbnailJPEG(tx *gorm.DB, media *models.Media, thumbnail_name string, photoCachePath string, baseImagePath string, mediaURL *models.MediaURL) (*models.MediaURL, error) {
|
func generateSaveThumbnailJPEG(tx *gorm.DB, media *models.Media, thumbnail_name string, photoCachePath string, baseImagePath string, mediaURL *models.MediaURL) (*models.MediaURL, error) {
|
||||||
thumbOutputPath := path.Join(photoCachePath, thumbnail_name)
|
thumbOutputPath := path.Join(photoCachePath, thumbnail_name)
|
||||||
|
|
||||||
thumbSize, err := media_encoding.EncodeThumbnail(baseImagePath, thumbOutputPath)
|
thumbSize, err := media_encoding.EncodeThumbnail(tx, baseImagePath, thumbOutputPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "could not create thumbnail cached image")
|
return nil, errors.Wrap(err, "could not create thumbnail cached image")
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { useIsAdmin } from '../../components/routes/AuthorizedRoute'
|
||||||
import Layout from '../../components/layout/Layout'
|
import Layout from '../../components/layout/Layout'
|
||||||
import ScannerSection from './ScannerSection'
|
import ScannerSection from './ScannerSection'
|
||||||
import UserPreferences from './UserPreferences'
|
import UserPreferences from './UserPreferences'
|
||||||
|
import ThumbnailPreferences from './ThumbnailPreferences'
|
||||||
import UsersTable from './Users/UsersTable'
|
import UsersTable from './Users/UsersTable'
|
||||||
import VersionInfo from './VersionInfo'
|
import VersionInfo from './VersionInfo'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
|
@ -46,6 +47,7 @@ const SettingsPage = () => {
|
||||||
<>
|
<>
|
||||||
<ScannerSection />
|
<ScannerSection />
|
||||||
<UsersTable />
|
<UsersTable />
|
||||||
|
<ThumbnailPreferences />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<VersionInfo />
|
<VersionInfo />
|
||||||
|
|
|
@ -0,0 +1,192 @@
|
||||||
|
import { gql } from '@apollo/client'
|
||||||
|
import React, { useRef, useState } from 'react'
|
||||||
|
import { useMutation, useQuery } from '@apollo/client'
|
||||||
|
import { InputLabelDescription, InputLabelTitle } from './SettingsPage'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { thumbnailMethodQuery } from './__generated__/thumbnailMethodQuery'
|
||||||
|
import {
|
||||||
|
setThumbnailMethodMutation,
|
||||||
|
setThumbnailMethodMutationVariables,
|
||||||
|
} from './__generated__/setThumbnailMethodMutation'
|
||||||
|
import Dropdown, { DropdownItem } from '../../primitives/form/Dropdown'
|
||||||
|
import Loader from '../../primitives/Loader'
|
||||||
|
|
||||||
|
export const THUMBNAIL_METHOD_QUERY = gql`
|
||||||
|
query thumbnailMethodQuery {
|
||||||
|
siteInfo {
|
||||||
|
thumbnailMethod
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export const SET_THUMBNAIL_METHOD_MUTATION = gql`
|
||||||
|
mutation setThumbnailMethodMutation($method: Int!) {
|
||||||
|
setThumbnailDownsampleMethod(method: $method)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const ThumbnailPreferences = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
const downsampleMethodServerValue = useRef<null | number>(null)
|
||||||
|
const [downsampleMethod, setDownsampleMethod] = useState(0)
|
||||||
|
|
||||||
|
const downsampleMethodQuery = useQuery<thumbnailMethodQuery>(
|
||||||
|
THUMBNAIL_METHOD_QUERY,
|
||||||
|
{
|
||||||
|
onCompleted(data) {
|
||||||
|
setDownsampleMethod(data.siteInfo.thumbnailMethod)
|
||||||
|
downsampleMethodServerValue.current = data.siteInfo.thumbnailMethod
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const [setDownsampleMutation, downsampleMutationData] = useMutation<
|
||||||
|
setThumbnailMethodMutation,
|
||||||
|
setThumbnailMethodMutationVariables
|
||||||
|
>(SET_THUMBNAIL_METHOD_MUTATION)
|
||||||
|
|
||||||
|
const updateDownsampleMethod = (downsampleMethod: number) => {
|
||||||
|
if (downsampleMethodServerValue.current != downsampleMethod) {
|
||||||
|
downsampleMethodServerValue.current = downsampleMethod
|
||||||
|
setDownsampleMutation({
|
||||||
|
variables: {
|
||||||
|
method: downsampleMethod,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const methodItems: DropdownItem[] = [
|
||||||
|
{
|
||||||
|
label: t(
|
||||||
|
'settings.thumbnails.downsample_method.nearest_neighbor',
|
||||||
|
'Nearest Neighbor'
|
||||||
|
),
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('settings.thumbnails.downsample_method.box', 'Box'),
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('settings.thumbnails.downsample_method.linear', 'Linear'),
|
||||||
|
value: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t(
|
||||||
|
'settings.thumbnails.downsample_method.mitchell_netravali',
|
||||||
|
'Mitchell-Netravali'
|
||||||
|
),
|
||||||
|
value: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t(
|
||||||
|
'settings.thumbnails.downsample_method.catmull_rom',
|
||||||
|
'Catmull-Rom'
|
||||||
|
),
|
||||||
|
value: 4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('settings.thumbnails.downsample_method.Lanczos', 'Lanczos'),
|
||||||
|
value: 5,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
// const [enablePeriodicScanner, setEnablePeriodicScanner] = useState(false)
|
||||||
|
// const [thumbnailMethod, setThumbnailMethod] = useState({
|
||||||
|
// value: 0,
|
||||||
|
// unit: TimeUnit.Second,
|
||||||
|
// })
|
||||||
|
//
|
||||||
|
// const scanIntervalServerValue = useRef<number | null>(null)
|
||||||
|
//
|
||||||
|
// const scanIntervalQuery = useQuery<scanIntervalQuery>(SCAN_INTERVAL_QUERY, {
|
||||||
|
// onCompleted(data) {
|
||||||
|
// const queryScanInterval = data.siteInfo.periodicScanInterval
|
||||||
|
//
|
||||||
|
// if (queryScanInterval == 0) {
|
||||||
|
// setScanInterval({
|
||||||
|
// unit: TimeUnit.Second,
|
||||||
|
// value: 0,
|
||||||
|
// })
|
||||||
|
// } else {
|
||||||
|
// setScanInterval(
|
||||||
|
// convertToAppropriateUnit({
|
||||||
|
// unit: TimeUnit.Second,
|
||||||
|
// value: queryScanInterval,
|
||||||
|
// })
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// setEnablePeriodicScanner(queryScanInterval > 0)
|
||||||
|
// },
|
||||||
|
// })
|
||||||
|
//
|
||||||
|
// const [setScanIntervalMutation, { loading: scanIntervalMutationLoading }] =
|
||||||
|
// useMutation<
|
||||||
|
// changeScanIntervalMutation,
|
||||||
|
// changeScanIntervalMutationVariables
|
||||||
|
// >(SCAN_INTERVAL_MUTATION)
|
||||||
|
//
|
||||||
|
// const onScanIntervalCheckboxChange = (checked: boolean) => {
|
||||||
|
// setEnablePeriodicScanner(checked)
|
||||||
|
//
|
||||||
|
// onScanIntervalUpdate(
|
||||||
|
// checked ? scanInterval : { value: 0, unit: TimeUnit.Second }
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// const onScanIntervalUpdate = (scanInterval: TimeValue) => {
|
||||||
|
// const seconds = convertToSeconds(scanInterval)
|
||||||
|
//
|
||||||
|
// if (scanIntervalServerValue.current != seconds) {
|
||||||
|
// setScanIntervalMutation({
|
||||||
|
// variables: {
|
||||||
|
// interval: seconds,
|
||||||
|
// },
|
||||||
|
// })
|
||||||
|
// scanIntervalServerValue.current = seconds
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="mt-4">
|
||||||
|
<label htmlFor="thumbnail_method_field">
|
||||||
|
<InputLabelTitle>
|
||||||
|
{t('settings.thumbnails.field.label', 'Downsampling method')}
|
||||||
|
</InputLabelTitle>
|
||||||
|
<InputLabelDescription>
|
||||||
|
{t(
|
||||||
|
'settings.thumbnails.field.description',
|
||||||
|
'The filter to use when generating thumbnails'
|
||||||
|
)}
|
||||||
|
</InputLabelDescription>
|
||||||
|
</label>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Dropdown
|
||||||
|
aria-label="Method"
|
||||||
|
items={methodItems}
|
||||||
|
selected={downsampleMethod}
|
||||||
|
setSelected={value => {
|
||||||
|
setDownsampleMethod(value)
|
||||||
|
updateDownsampleMethod(value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Loader
|
||||||
|
active={downsampleMethodQuery.loading || downsampleMutationData.loading}
|
||||||
|
size="small"
|
||||||
|
style={{ marginLeft: 16 }}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ThumbnailPreferences
|
||||||
|
|
||||||
|
// <h3 className="font-semibold text-lg mt-4 mb-2">
|
||||||
|
// {t('settings.thumbnails.title', 'Thumbnail preferences')}
|
||||||
|
// </h3>
|
|
@ -0,0 +1,19 @@
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @generated
|
||||||
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
// ====================================================
|
||||||
|
// GraphQL mutation operation: setThumbnailMethodMutation
|
||||||
|
// ====================================================
|
||||||
|
|
||||||
|
export interface setThumbnailMethodMutation {
|
||||||
|
/**
|
||||||
|
* Set the filter to be used when generating thumbnails
|
||||||
|
*/
|
||||||
|
setThumbnailDownsampleMethod: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface setThumbnailMethodMutationVariables {
|
||||||
|
method: number
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @generated
|
||||||
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
// ====================================================
|
||||||
|
// GraphQL query operation: thumbnailMethodQuery
|
||||||
|
// ====================================================
|
||||||
|
|
||||||
|
export interface thumbnailMethodQuery_siteInfo {
|
||||||
|
__typename: 'SiteInfo'
|
||||||
|
/**
|
||||||
|
* The filter to use when generating thumbnails
|
||||||
|
*/
|
||||||
|
thumbnailMethod: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface thumbnailMethodQuery {
|
||||||
|
siteInfo: thumbnailMethodQuery_siteInfo
|
||||||
|
}
|
Loading…
Reference in New Issue