diff --git a/api/custom/version.go b/api/custom/version.go index 85b79695726fdfff83156d82cfbc933c9dfec0c9..d5ac8e6dd77023b60f60ec84152e216375d83ee3 100644 --- a/api/custom/version.go +++ b/api/custom/version.go @@ -9,13 +9,14 @@ import ( ) func GetVersion(r *http.Request, rctx rcontext.RequestContext, user api.UserInfo) interface{} { + unstableFeatures := make(map[string]bool) + unstableFeatures["xyz.amorgan.blurhash"] = rctx.Config.Features.MSC2448Blurhash.Enabled + return &api.DoNotCacheResponse{ Payload: map[string]interface{}{ - "Version": version.Version, - "GitCommit": version.GitCommit, - "unstable_features": []string{ - "xyz.amorgan.blurhash", - }, + "Version": version.Version, + "GitCommit": version.GitCommit, + "unstable_features": unstableFeatures, }, } } diff --git a/api/r0/upload.go b/api/r0/upload.go index 4b6692714ed557b52f6118559bf0c3f8c1813000..20518131b3bca46205640e6208de905487b49af1 100644 --- a/api/r0/upload.go +++ b/api/r0/upload.go @@ -58,13 +58,19 @@ func UploadMedia(r *http.Request, rctx rcontext.RequestContext, user api.UserInf return api.InternalServerError("Unexpected Error") } - hash, err := info_controller.GetOrCalculateBlurhash(media, rctx) - if err != nil { - rctx.Log.Warn("Failed to calculate blurhash: " + err.Error()) + if rctx.Config.Features.MSC2448Blurhash.Enabled { + hash, err := info_controller.GetOrCalculateBlurhash(media, rctx) + if err != nil { + rctx.Log.Warn("Failed to calculate blurhash: " + err.Error()) + } + + return &MediaUploadedResponse{ + ContentUri: media.MxcUri(), + Blurhash: hash, + } } return &MediaUploadedResponse{ ContentUri: media.MxcUri(), - Blurhash: hash, } } diff --git a/api/webserver/webserver.go b/api/webserver/webserver.go index 86b5c3d3760447a2ba32acec36a69b1fd2b2f944..822381e5846ac39cf61ff4a250ce314983a9e602 100644 --- a/api/webserver/webserver.go +++ b/api/webserver/webserver.go @@ -135,8 +135,9 @@ func Init() *sync.WaitGroup { routes["/_matrix/media/"+version+"/info/{server:[a-zA-Z0-9.:\\-_]+}/{mediaId:[a-zA-Z0-9.\\-_]+}"] = route{"GET", infoHandler} routes["/_matrix/media/"+version+"/download/{server:[a-zA-Z0-9.:\\-_]+}/{mediaId:[a-zA-Z0-9.\\-_]+}"] = route{"DELETE", purgeOneHandler} - // MSC2448: Blurhash - routes["/_matrix/media/"+version+"/xyz.amorgan/upload"] = route{"POST", uploadHandler} + if config.Get().Features.MSC2448Blurhash.Enabled { + routes["/_matrix/media/"+version+"/xyz.amorgan/upload"] = route{"POST", uploadHandler} + } } } diff --git a/common/config/conf_min_shared.go b/common/config/conf_min_shared.go index 44ef3fcd3a292c834df28ba3b4365422aed8c5e2..d2e1ada1d0328bb32129ba21421c6a4f594d3965 100644 --- a/common/config/conf_min_shared.go +++ b/common/config/conf_min_shared.go @@ -7,6 +7,7 @@ type MinimumRepoConfig struct { Identicons IdenticonsConfig `yaml:"identicons"` Quarantine QuarantineConfig `yaml:"quarantine"` TimeoutSeconds TimeoutsConfig `yaml:"timeouts"` + Features FeatureConfig `yaml:"featureSupport"` } func NewDefaultMinimumRepoConfig() MinimumRepoConfig { @@ -38,5 +39,17 @@ func NewDefaultMinimumRepoConfig() MinimumRepoConfig { ClientServer: 30, Federation: 120, }, + Features: FeatureConfig{ + MSC2448Blurhash: MSC2448Config{ + Enabled: false, + MaxRenderWidth: 1024, + MaxRenderHeight: 1024, + GenerateWidth: 64, + GenerateHeight: 64, + XComponents: 4, + YComponents: 3, + Punch: 1, + }, + }, } } diff --git a/common/config/models_domain.go b/common/config/models_domain.go index 98d60c9acecd500c43b450b9fca96c1f73970f1e..6c11d72b5349ff54147c880471a1d732e1990588 100644 --- a/common/config/models_domain.go +++ b/common/config/models_domain.go @@ -72,3 +72,18 @@ type TimeoutsConfig struct { Federation int `yaml:"federationTimeoutSeconds"` ClientServer int `yaml:"clientServerTimeoutSeconds"` } + +type FeatureConfig struct { + MSC2448Blurhash MSC2448Config `yaml:"MSC2448"` +} + +type MSC2448Config struct { + Enabled bool `yaml:"enabled"` + MaxRenderWidth int `yaml:"maxWidth"` + MaxRenderHeight int `yaml:"maxHeight"` + GenerateWidth int `yaml:"thumbWidth"` + GenerateHeight int `yaml:"thumbHeight"` + XComponents int `yaml:"xComponents"` + YComponents int `yaml:"yComponents"` + Punch int `yaml:"punch"` +} diff --git a/common/config/watch.go b/common/config/watch.go index 1e64ff8c46bbeaabad7e8c8de7bf542cd015c60c..62c8fd92d1928331a125e2f0d198d8e9d9a19b83 100644 --- a/common/config/watch.go +++ b/common/config/watch.go @@ -60,7 +60,8 @@ func onFileChanged() { bindPortChange := configNew.General.Port != configNow.General.Port forwardAddressChange := configNew.General.TrustAnyForward != configNow.General.TrustAnyForward forwardedHostChange := configNew.General.UseForwardedHost != configNow.General.UseForwardedHost - if bindAddressChange || bindPortChange || forwardAddressChange || forwardedHostChange { + featureChanged := hasWebFeatureChanged(configNew, configNow) + if bindAddressChange || bindPortChange || forwardAddressChange || forwardedHostChange || featureChanged { logrus.Warn("Webserver configuration changed - remounting") globals.WebReloadChan <- true } @@ -93,3 +94,11 @@ func onFileChanged() { logrus.Info("Restarting recurring tasks") globals.RecurringTasksReloadChan <- true } + +func hasWebFeatureChanged(configNew *MainRepoConfig, configNow *MainRepoConfig) bool { + if configNew.Features.MSC2448Blurhash.Enabled != configNow.Features.MSC2448Blurhash.Enabled { + return true + } + + return false +} diff --git a/config.sample.yaml b/config.sample.yaml index b6df78cc9a7e8af16a919664c1f5d85377118641..4b0f87f5bcc298b7fbcb7f3eb05525f517f29647 100644 --- a/config.sample.yaml +++ b/config.sample.yaml @@ -392,3 +392,28 @@ metrics: # The port to listen on. Cannot be the same as the general web server port. port: 9000 + +# Options for controlling various MSCs/unstable features of the media repo +# Sections of this config might disappear or be added over time. By default all +# features are disabled in here and must be explicitly enabled to be used. +featureSupport: + # MSC2248 - Blurhash + MSC2448: + # Whether or not this MSC is enabled for use in the media repo + enabled: false + + # Maximum dimensions for converting a blurhash to an image + maxWidth: 1024 + maxHeight: 1024 + + # Thumbnail size in pixels to use to generate the blurhash string + thumbWidth: 64 + thumbHeight: 64 + + # The X and Y components to use. Higher numbers blur less, lower numbers blur more. + xComponents: 4 + yComponents: 3 + + # The amount of contrast to apply when converting a blurhash to an image. Lower values + # make the effect more subtle, larger values make it stronger. + punch: 1 diff --git a/controllers/info_controller/info_controller.go b/controllers/info_controller/info_controller.go index 223fe32549fb1754423b6fac06152d5de0f33a20..69c7eb4611f866e2d4ad3ce151ee844f81a1caa2 100644 --- a/controllers/info_controller/info_controller.go +++ b/controllers/info_controller/info_controller.go @@ -40,7 +40,7 @@ func GetOrCalculateBlurhash(media *types.Media, rctx rcontext.RequestContext) (s // Resize the image to make the blurhash a bit more reasonable to calculate rctx.Log.Info("Resizing image for blurhash (faster calculation)") - smallImg := imaging.Fill(imgSrc, 128, 128, imaging.Center, imaging.Lanczos) + smallImg := imaging.Fill(imgSrc, rctx.Config.Features.MSC2448Blurhash.GenerateWidth, rctx.Config.Features.MSC2448Blurhash.GenerateHeight, imaging.Center, imaging.Lanczos) imgBuf := &bytes.Buffer{} err = imaging.Encode(imgBuf, smallImg, imaging.PNG) if err != nil { @@ -52,7 +52,7 @@ func GetOrCalculateBlurhash(media *types.Media, rctx rcontext.RequestContext) (s } rctx.Log.Info("Calculating blurhash") - encoded, err := blurhash.Encode(4, 3, &decoded) + encoded, err := blurhash.Encode(rctx.Config.Features.MSC2448Blurhash.XComponents, rctx.Config.Features.MSC2448Blurhash.YComponents, &decoded) if err != nil { return "", err } diff --git a/docs/config.md b/docs/config.md index f316436b1ff3860c266938c1221f7407fae909d7..0e98b63da0a82c61c2cc9ebda2f5f640ba98ad78 100644 --- a/docs/config.md +++ b/docs/config.md @@ -65,3 +65,6 @@ identicons: Per-domain configs can also be layered - just ensure that each layer has the `homeserver` property in it. They inherit from the main config for options not defined in their layers. + +Note: all feature configs which require webserver routes to be added will need to be additionally defined in the main +config as enabled or disabled, then turned on and off for individual domains.