diff --git a/docs/configuration/media.md b/docs/configuration/media.md index 4e222c6c7..e49b59dd3 100644 --- a/docs/configuration/media.md +++ b/docs/configuration/media.md @@ -18,6 +18,24 @@ # Default: 40MiB (41943040 bytes) media-local-max-size: 40MiB +# Size. Size in bytes of max image size referred to on /api/v_/instance endpoints, +# used by applications like Tusky to automatically scale locally uploaded media. +# +# Leaving this unset will default to media-local-max-size. +# +# Examples: [64, 500, 5MiB, 5MB, 50M] +# Default: unset +media-image-size-hint: 5MiB + +# Size. Size in bytes of max video size referred to on /api/v_/instance endpoints, +# used by applications like Tusky to automatically scale locally uploaded media. +# +# Leaving this unset will default to media-local-max-size. +# +# Examples: [64, 4096, 4MiB, 4MB, 40M] +# Default: unset +media-video-size-hint: 40MiB + # Size. Max size in bytes of media to download from other instances. # # Lowering this limit may cause your instance not to fetch post media. diff --git a/example/config.yaml b/example/config.yaml index d3de40791..644b51575 100644 --- a/example/config.yaml +++ b/example/config.yaml @@ -471,6 +471,24 @@ accounts-custom-css-length: 10000 # Default: 40MiB (41943040 bytes) media-local-max-size: 40MiB +# Size. Size in bytes of max image size referred to on /api/v_/instance endpoints, +# used by applications like Tusky to automatically scale locally uploaded media. +# +# Leaving this unset will default to media-local-max-size. +# +# Examples: [64, 500, 5MiB, 5MB, 50M] +# Default: unset +media-image-size-hint: 5MiB + +# Size. Size in bytes of max video size referred to on /api/v_/instance endpoints, +# used by applications like Tusky to automatically scale locally uploaded media. +# +# Leaving this unset will default to media-local-max-size. +# +# Examples: [64, 4096, 4MiB, 4MB, 40M] +# Default: unset +media-video-size-hint: 40MiB + # Size. Max size in bytes of media to download from other instances. # # Lowering this limit may cause your instance not to fetch post media. diff --git a/internal/api/client/instance/instancepatch_test.go b/internal/api/client/instance/instancepatch_test.go index 504fd7eb9..3a362f27c 100644 --- a/internal/api/client/instance/instancepatch_test.go +++ b/internal/api/client/instance/instancepatch_test.go @@ -130,10 +130,10 @@ func (suite *InstancePatchTestSuite) TestInstancePatch1() { "video/x-matroska" ], "image_size_limit": 41943040, - "image_matrix_limit": 16777216, + "image_matrix_limit": 9223372036854775807, "video_size_limit": 41943040, - "video_frame_rate_limit": 60, - "video_matrix_limit": 16777216 + "video_frame_rate_limit": 9223372036854775807, + "video_matrix_limit": 9223372036854775807 }, "polls": { "max_options": 6, @@ -271,10 +271,10 @@ func (suite *InstancePatchTestSuite) TestInstancePatch2() { "video/x-matroska" ], "image_size_limit": 41943040, - "image_matrix_limit": 16777216, + "image_matrix_limit": 9223372036854775807, "video_size_limit": 41943040, - "video_frame_rate_limit": 60, - "video_matrix_limit": 16777216 + "video_frame_rate_limit": 9223372036854775807, + "video_matrix_limit": 9223372036854775807 }, "polls": { "max_options": 6, @@ -412,10 +412,10 @@ func (suite *InstancePatchTestSuite) TestInstancePatch3() { "video/x-matroska" ], "image_size_limit": 41943040, - "image_matrix_limit": 16777216, + "image_matrix_limit": 9223372036854775807, "video_size_limit": 41943040, - "video_frame_rate_limit": 60, - "video_matrix_limit": 16777216 + "video_frame_rate_limit": 9223372036854775807, + "video_matrix_limit": 9223372036854775807 }, "polls": { "max_options": 6, @@ -604,10 +604,10 @@ func (suite *InstancePatchTestSuite) TestInstancePatch6() { "video/x-matroska" ], "image_size_limit": 41943040, - "image_matrix_limit": 16777216, + "image_matrix_limit": 9223372036854775807, "video_size_limit": 41943040, - "video_frame_rate_limit": 60, - "video_matrix_limit": 16777216 + "video_frame_rate_limit": 9223372036854775807, + "video_matrix_limit": 9223372036854775807 }, "polls": { "max_options": 6, @@ -767,10 +767,10 @@ func (suite *InstancePatchTestSuite) TestInstancePatch8() { "video/x-matroska" ], "image_size_limit": 41943040, - "image_matrix_limit": 16777216, + "image_matrix_limit": 9223372036854775807, "video_size_limit": 41943040, - "video_frame_rate_limit": 60, - "video_matrix_limit": 16777216 + "video_frame_rate_limit": 9223372036854775807, + "video_matrix_limit": 9223372036854775807 }, "polls": { "max_options": 6, @@ -949,10 +949,10 @@ func (suite *InstancePatchTestSuite) TestInstancePatch9() { "video/x-matroska" ], "image_size_limit": 41943040, - "image_matrix_limit": 16777216, + "image_matrix_limit": 9223372036854775807, "video_size_limit": 41943040, - "video_frame_rate_limit": 60, - "video_matrix_limit": 16777216 + "video_frame_rate_limit": 9223372036854775807, + "video_matrix_limit": 9223372036854775807 }, "polls": { "max_options": 6, diff --git a/internal/config/config.go b/internal/config/config.go index 4a40e9c13..9001b61d0 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -98,6 +98,8 @@ type Configuration struct { MediaRemoteCacheDays int `name:"media-remote-cache-days" usage:"Number of days to locally cache media from remote instances. If set to 0, remote media will be kept indefinitely."` MediaEmojiLocalMaxSize bytesize.Size `name:"media-emoji-local-max-size" usage:"Max size in bytes of emojis uploaded to this instance via the admin API."` MediaEmojiRemoteMaxSize bytesize.Size `name:"media-emoji-remote-max-size" usage:"Max size in bytes of emojis to download from other instances."` + MediaImageSizeHint bytesize.Size `name:"media-image-size-hint" usage:"Size in bytes of max image size referred to on /api/v_/instance endpoints (else, local max size)"` + MediaVideoSizeHint bytesize.Size `name:"media-video-size-hint" usage:"Size in bytes of max video size referred to on /api/v_/instance endpoints (else, local max size)"` MediaLocalMaxSize bytesize.Size `name:"media-local-max-size" usage:"Max size in bytes of media uploaded to this instance via API"` MediaRemoteMaxSize bytesize.Size `name:"media-remote-max-size" usage:"Max size in bytes of media to download from other instances"` MediaCleanupFrom string `name:"media-cleanup-from" usage:"Time of day from which to start running media cleanup/prune jobs. Should be in the format 'hh:mm:ss', eg., '15:04:05'."` diff --git a/internal/config/helpers.gen.go b/internal/config/helpers.gen.go index d25d6cca8..2a7e5b6ad 100644 --- a/internal/config/helpers.gen.go +++ b/internal/config/helpers.gen.go @@ -1225,6 +1225,56 @@ func GetMediaEmojiRemoteMaxSize() bytesize.Size { return global.GetMediaEmojiRem // SetMediaEmojiRemoteMaxSize safely sets the value for global configuration 'MediaEmojiRemoteMaxSize' field func SetMediaEmojiRemoteMaxSize(v bytesize.Size) { global.SetMediaEmojiRemoteMaxSize(v) } +// GetMediaImageSizeHint safely fetches the Configuration value for state's 'MediaImageSizeHint' field +func (st *ConfigState) GetMediaImageSizeHint() (v bytesize.Size) { + st.mutex.RLock() + v = st.config.MediaImageSizeHint + st.mutex.RUnlock() + return +} + +// SetMediaImageSizeHint safely sets the Configuration value for state's 'MediaImageSizeHint' field +func (st *ConfigState) SetMediaImageSizeHint(v bytesize.Size) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.MediaImageSizeHint = v + st.reloadToViper() +} + +// MediaImageSizeHintFlag returns the flag name for the 'MediaImageSizeHint' field +func MediaImageSizeHintFlag() string { return "media-image-size-hint" } + +// GetMediaImageSizeHint safely fetches the value for global configuration 'MediaImageSizeHint' field +func GetMediaImageSizeHint() bytesize.Size { return global.GetMediaImageSizeHint() } + +// SetMediaImageSizeHint safely sets the value for global configuration 'MediaImageSizeHint' field +func SetMediaImageSizeHint(v bytesize.Size) { global.SetMediaImageSizeHint(v) } + +// GetMediaVideoSizeHint safely fetches the Configuration value for state's 'MediaVideoSizeHint' field +func (st *ConfigState) GetMediaVideoSizeHint() (v bytesize.Size) { + st.mutex.RLock() + v = st.config.MediaVideoSizeHint + st.mutex.RUnlock() + return +} + +// SetMediaVideoSizeHint safely sets the Configuration value for state's 'MediaVideoSizeHint' field +func (st *ConfigState) SetMediaVideoSizeHint(v bytesize.Size) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.MediaVideoSizeHint = v + st.reloadToViper() +} + +// MediaVideoSizeHintFlag returns the flag name for the 'MediaVideoSizeHint' field +func MediaVideoSizeHintFlag() string { return "media-video-size-hint" } + +// GetMediaVideoSizeHint safely fetches the value for global configuration 'MediaVideoSizeHint' field +func GetMediaVideoSizeHint() bytesize.Size { return global.GetMediaVideoSizeHint() } + +// SetMediaVideoSizeHint safely sets the value for global configuration 'MediaVideoSizeHint' field +func SetMediaVideoSizeHint(v bytesize.Size) { global.SetMediaVideoSizeHint(v) } + // GetMediaLocalMaxSize safely fetches the Configuration value for state's 'MediaLocalMaxSize' field func (st *ConfigState) GetMediaLocalMaxSize() (v bytesize.Size) { st.mutex.RLock() diff --git a/internal/typeutils/internaltofrontend.go b/internal/typeutils/internaltofrontend.go index 03b24fc9c..2a6d495d7 100644 --- a/internal/typeutils/internaltofrontend.go +++ b/internal/typeutils/internaltofrontend.go @@ -18,9 +18,11 @@ package typeutils import ( + "cmp" "context" "errors" "fmt" + "math" "slices" "strconv" "strings" @@ -42,16 +44,13 @@ import ( ) const ( - instanceStatusesCharactersReservedPerURL = 25 - instanceMediaAttachmentsImageMatrixLimit = 16777216 // width * height - instanceMediaAttachmentsVideoMatrixLimit = 16777216 // width * height - instanceMediaAttachmentsVideoFrameRateLimit = 60 - instancePollsMinExpiration = 300 // seconds - instancePollsMaxExpiration = 2629746 // seconds - instanceAccountsMaxFeaturedTags = 10 - instanceAccountsMaxProfileFields = 6 // FIXME: https://github.com/superseriousbusiness/gotosocial/issues/1876 - instanceSourceURL = "https://github.com/superseriousbusiness/gotosocial" - instanceMastodonVersion = "3.5.3" + instanceStatusesCharactersReservedPerURL = 25 + instancePollsMinExpiration = 300 // seconds + instancePollsMaxExpiration = 2629746 // seconds + instanceAccountsMaxFeaturedTags = 10 + instanceAccountsMaxProfileFields = 6 // FIXME: https://github.com/superseriousbusiness/gotosocial/issues/1876 + instanceSourceURL = "https://github.com/superseriousbusiness/gotosocial" + instanceMastodonVersion = "3.5.3" ) var instanceStatusesSupportedMimeTypes = []string{ @@ -1563,11 +1562,24 @@ func (c *Converter) InstanceToAPIV1Instance(ctx context.Context, i *gtsmodel.Ins instance.Configuration.Statuses.CharactersReservedPerURL = instanceStatusesCharactersReservedPerURL instance.Configuration.Statuses.SupportedMimeTypes = instanceStatusesSupportedMimeTypes instance.Configuration.MediaAttachments.SupportedMimeTypes = media.SupportedMIMETypes - instance.Configuration.MediaAttachments.ImageSizeLimit = int(config.GetMediaRemoteMaxSize()) // #nosec G115 -- Already validated. - instance.Configuration.MediaAttachments.ImageMatrixLimit = instanceMediaAttachmentsImageMatrixLimit - instance.Configuration.MediaAttachments.VideoSizeLimit = int(config.GetMediaRemoteMaxSize()) // #nosec G115 -- Already validated. - instance.Configuration.MediaAttachments.VideoFrameRateLimit = instanceMediaAttachmentsVideoFrameRateLimit - instance.Configuration.MediaAttachments.VideoMatrixLimit = instanceMediaAttachmentsVideoMatrixLimit + + // NOTE: we use the local max sizes here + // as it hints to apps like Tusky for image + // compression of locally uploaded media. + // + // TODO: return local / remote depending + // on authorized endpoint user (if any)? + localMax := config.GetMediaLocalMaxSize() + imageSz := cmp.Or(config.GetMediaImageSizeHint(), localMax) + videoSz := cmp.Or(config.GetMediaVideoSizeHint(), localMax) + instance.Configuration.MediaAttachments.ImageSizeLimit = int(imageSz) // #nosec G115 -- Already validated. + instance.Configuration.MediaAttachments.VideoSizeLimit = int(videoSz) // #nosec G115 -- Already validated. + + // we don't actually set any limits on these. set to max possible. + instance.Configuration.MediaAttachments.ImageMatrixLimit = math.MaxInt + instance.Configuration.MediaAttachments.VideoFrameRateLimit = math.MaxInt + instance.Configuration.MediaAttachments.VideoMatrixLimit = math.MaxInt + instance.Configuration.Polls.MaxOptions = config.GetStatusesPollMaxOptions() instance.Configuration.Polls.MaxCharactersPerOption = config.GetStatusesPollOptionMaxChars() instance.Configuration.Polls.MinExpiration = instancePollsMinExpiration @@ -1713,11 +1725,24 @@ func (c *Converter) InstanceToAPIV2Instance(ctx context.Context, i *gtsmodel.Ins instance.Configuration.Statuses.CharactersReservedPerURL = instanceStatusesCharactersReservedPerURL instance.Configuration.Statuses.SupportedMimeTypes = instanceStatusesSupportedMimeTypes instance.Configuration.MediaAttachments.SupportedMimeTypes = media.SupportedMIMETypes - instance.Configuration.MediaAttachments.ImageSizeLimit = int(config.GetMediaRemoteMaxSize()) // #nosec G115 -- Already validated. - instance.Configuration.MediaAttachments.ImageMatrixLimit = instanceMediaAttachmentsImageMatrixLimit - instance.Configuration.MediaAttachments.VideoSizeLimit = int(config.GetMediaRemoteMaxSize()) // #nosec G115 -- Already validated. - instance.Configuration.MediaAttachments.VideoFrameRateLimit = instanceMediaAttachmentsVideoFrameRateLimit - instance.Configuration.MediaAttachments.VideoMatrixLimit = instanceMediaAttachmentsVideoMatrixLimit + + // NOTE: we use the local max sizes here + // as it hints to apps like Tusky for image + // compression of locally uploaded media. + // + // TODO: return local / remote depending + // on authorized endpoint user (if any)? + localMax := config.GetMediaLocalMaxSize() + imageSz := cmp.Or(config.GetMediaImageSizeHint(), localMax) + videoSz := cmp.Or(config.GetMediaVideoSizeHint(), localMax) + instance.Configuration.MediaAttachments.ImageSizeLimit = int(imageSz) // #nosec G115 -- Already validated. + instance.Configuration.MediaAttachments.VideoSizeLimit = int(videoSz) // #nosec G115 -- Already validated. + + // we don't actually set any limits on these. set to max possible. + instance.Configuration.MediaAttachments.ImageMatrixLimit = math.MaxInt + instance.Configuration.MediaAttachments.VideoFrameRateLimit = math.MaxInt + instance.Configuration.MediaAttachments.VideoMatrixLimit = math.MaxInt + instance.Configuration.Polls.MaxOptions = config.GetStatusesPollMaxOptions() instance.Configuration.Polls.MaxCharactersPerOption = config.GetStatusesPollOptionMaxChars() instance.Configuration.Polls.MinExpiration = instancePollsMinExpiration diff --git a/internal/typeutils/internaltofrontend_test.go b/internal/typeutils/internaltofrontend_test.go index cf11655ca..6c318e851 100644 --- a/internal/typeutils/internaltofrontend_test.go +++ b/internal/typeutils/internaltofrontend_test.go @@ -1968,10 +1968,10 @@ func (suite *InternalToFrontendTestSuite) TestInstanceV1ToFrontend() { "video/x-matroska" ], "image_size_limit": 41943040, - "image_matrix_limit": 16777216, + "image_matrix_limit": 9223372036854775807, "video_size_limit": 41943040, - "video_frame_rate_limit": 60, - "video_matrix_limit": 16777216 + "video_frame_rate_limit": 9223372036854775807, + "video_matrix_limit": 9223372036854775807 }, "polls": { "max_options": 6, @@ -2113,10 +2113,10 @@ func (suite *InternalToFrontendTestSuite) TestInstanceV2ToFrontend() { "video/x-matroska" ], "image_size_limit": 41943040, - "image_matrix_limit": 16777216, + "image_matrix_limit": 9223372036854775807, "video_size_limit": 41943040, - "video_frame_rate_limit": 60, - "video_matrix_limit": 16777216 + "video_frame_rate_limit": 9223372036854775807, + "video_matrix_limit": 9223372036854775807 }, "polls": { "max_options": 6, diff --git a/test/envparsing.sh b/test/envparsing.sh index ac6c2edc0..503bdd817 100755 --- a/test/envparsing.sh +++ b/test/envparsing.sh @@ -129,9 +129,11 @@ EXPECT=$(cat << "EOF" "media-emoji-local-max-size": 420, "media-emoji-remote-max-size": 420, "media-ffmpeg-pool-size": 8, + "media-image-size-hint": 5242880, "media-local-max-size": 420, "media-remote-cache-days": 30, "media-remote-max-size": 420, + "media-video-size-hint": 41943040, "metrics-auth-enabled": false, "metrics-auth-password": "", "metrics-auth-username": "", @@ -244,12 +246,14 @@ GTS_ACCOUNTS_REGISTRATION_OPEN=true \ GTS_ACCOUNTS_REASON_REQUIRED=false \ GTS_MEDIA_DESCRIPTION_MIN_CHARS=69 \ GTS_MEDIA_DESCRIPTION_MAX_CHARS=5000 \ +GTS_MEDIA_IMAGE_SIZE_HINT='5MiB' \ GTS_MEDIA_LOCAL_MAX_SIZE=420 \ GTS_MEDIA_REMOTE_MAX_SIZE=420 \ GTS_MEDIA_REMOTE_CACHE_DAYS=30 \ GTS_MEDIA_EMOJI_LOCAL_MAX_SIZE=420 \ GTS_MEDIA_EMOJI_REMOTE_MAX_SIZE=420 \ GTS_MEDIA_FFMPEG_POOL_SIZE=8 \ +GTS_MEDIA_VIDEO_SIZE_HINT='40MiB' \ GTS_METRICS_AUTH_ENABLED=false \ GTS_METRICS_ENABLED=false \ GTS_STORAGE_BACKEND='local' \