Skip to content

Commit f3b2eca

Browse files
[feature] add support for hinting via api/v_/instance preferred image / video max sizes (#3505)
* add support for hinting via api/v_/instance endpoints a preferred image / video size limit * fix tests expecting old default values
1 parent 8f288f1 commit f3b2eca

File tree

8 files changed

+161
-44
lines changed

8 files changed

+161
-44
lines changed

docs/configuration/media.md

+18
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,24 @@
1818
# Default: 40MiB (41943040 bytes)
1919
media-local-max-size: 40MiB
2020

21+
# Size. Size in bytes of max image size referred to on /api/v_/instance endpoints,
22+
# used by applications like Tusky to automatically scale locally uploaded media.
23+
#
24+
# Leaving this unset will default to media-local-max-size.
25+
#
26+
# Examples: [64, 500, 5MiB, 5MB, 50M]
27+
# Default: unset
28+
media-image-size-hint: 5MiB
29+
30+
# Size. Size in bytes of max video size referred to on /api/v_/instance endpoints,
31+
# used by applications like Tusky to automatically scale locally uploaded media.
32+
#
33+
# Leaving this unset will default to media-local-max-size.
34+
#
35+
# Examples: [64, 4096, 4MiB, 4MB, 40M]
36+
# Default: unset
37+
media-video-size-hint: 40MiB
38+
2139
# Size. Max size in bytes of media to download from other instances.
2240
#
2341
# Lowering this limit may cause your instance not to fetch post media.

example/config.yaml

+18
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,24 @@ accounts-custom-css-length: 10000
471471
# Default: 40MiB (41943040 bytes)
472472
media-local-max-size: 40MiB
473473

474+
# Size. Size in bytes of max image size referred to on /api/v_/instance endpoints,
475+
# used by applications like Tusky to automatically scale locally uploaded media.
476+
#
477+
# Leaving this unset will default to media-local-max-size.
478+
#
479+
# Examples: [64, 500, 5MiB, 5MB, 50M]
480+
# Default: unset
481+
media-image-size-hint: 5MiB
482+
483+
# Size. Size in bytes of max video size referred to on /api/v_/instance endpoints,
484+
# used by applications like Tusky to automatically scale locally uploaded media.
485+
#
486+
# Leaving this unset will default to media-local-max-size.
487+
#
488+
# Examples: [64, 4096, 4MiB, 4MB, 40M]
489+
# Default: unset
490+
media-video-size-hint: 40MiB
491+
474492
# Size. Max size in bytes of media to download from other instances.
475493
#
476494
# Lowering this limit may cause your instance not to fetch post media.

internal/api/client/instance/instancepatch_test.go

+18-18
Original file line numberDiff line numberDiff line change
@@ -130,10 +130,10 @@ func (suite *InstancePatchTestSuite) TestInstancePatch1() {
130130
"video/x-matroska"
131131
],
132132
"image_size_limit": 41943040,
133-
"image_matrix_limit": 16777216,
133+
"image_matrix_limit": 9223372036854775807,
134134
"video_size_limit": 41943040,
135-
"video_frame_rate_limit": 60,
136-
"video_matrix_limit": 16777216
135+
"video_frame_rate_limit": 9223372036854775807,
136+
"video_matrix_limit": 9223372036854775807
137137
},
138138
"polls": {
139139
"max_options": 6,
@@ -271,10 +271,10 @@ func (suite *InstancePatchTestSuite) TestInstancePatch2() {
271271
"video/x-matroska"
272272
],
273273
"image_size_limit": 41943040,
274-
"image_matrix_limit": 16777216,
274+
"image_matrix_limit": 9223372036854775807,
275275
"video_size_limit": 41943040,
276-
"video_frame_rate_limit": 60,
277-
"video_matrix_limit": 16777216
276+
"video_frame_rate_limit": 9223372036854775807,
277+
"video_matrix_limit": 9223372036854775807
278278
},
279279
"polls": {
280280
"max_options": 6,
@@ -412,10 +412,10 @@ func (suite *InstancePatchTestSuite) TestInstancePatch3() {
412412
"video/x-matroska"
413413
],
414414
"image_size_limit": 41943040,
415-
"image_matrix_limit": 16777216,
415+
"image_matrix_limit": 9223372036854775807,
416416
"video_size_limit": 41943040,
417-
"video_frame_rate_limit": 60,
418-
"video_matrix_limit": 16777216
417+
"video_frame_rate_limit": 9223372036854775807,
418+
"video_matrix_limit": 9223372036854775807
419419
},
420420
"polls": {
421421
"max_options": 6,
@@ -604,10 +604,10 @@ func (suite *InstancePatchTestSuite) TestInstancePatch6() {
604604
"video/x-matroska"
605605
],
606606
"image_size_limit": 41943040,
607-
"image_matrix_limit": 16777216,
607+
"image_matrix_limit": 9223372036854775807,
608608
"video_size_limit": 41943040,
609-
"video_frame_rate_limit": 60,
610-
"video_matrix_limit": 16777216
609+
"video_frame_rate_limit": 9223372036854775807,
610+
"video_matrix_limit": 9223372036854775807
611611
},
612612
"polls": {
613613
"max_options": 6,
@@ -767,10 +767,10 @@ func (suite *InstancePatchTestSuite) TestInstancePatch8() {
767767
"video/x-matroska"
768768
],
769769
"image_size_limit": 41943040,
770-
"image_matrix_limit": 16777216,
770+
"image_matrix_limit": 9223372036854775807,
771771
"video_size_limit": 41943040,
772-
"video_frame_rate_limit": 60,
773-
"video_matrix_limit": 16777216
772+
"video_frame_rate_limit": 9223372036854775807,
773+
"video_matrix_limit": 9223372036854775807
774774
},
775775
"polls": {
776776
"max_options": 6,
@@ -949,10 +949,10 @@ func (suite *InstancePatchTestSuite) TestInstancePatch9() {
949949
"video/x-matroska"
950950
],
951951
"image_size_limit": 41943040,
952-
"image_matrix_limit": 16777216,
952+
"image_matrix_limit": 9223372036854775807,
953953
"video_size_limit": 41943040,
954-
"video_frame_rate_limit": 60,
955-
"video_matrix_limit": 16777216
954+
"video_frame_rate_limit": 9223372036854775807,
955+
"video_matrix_limit": 9223372036854775807
956956
},
957957
"polls": {
958958
"max_options": 6,

internal/config/config.go

+2
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ type Configuration struct {
9898
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."`
9999
MediaEmojiLocalMaxSize bytesize.Size `name:"media-emoji-local-max-size" usage:"Max size in bytes of emojis uploaded to this instance via the admin API."`
100100
MediaEmojiRemoteMaxSize bytesize.Size `name:"media-emoji-remote-max-size" usage:"Max size in bytes of emojis to download from other instances."`
101+
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)"`
102+
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)"`
101103
MediaLocalMaxSize bytesize.Size `name:"media-local-max-size" usage:"Max size in bytes of media uploaded to this instance via API"`
102104
MediaRemoteMaxSize bytesize.Size `name:"media-remote-max-size" usage:"Max size in bytes of media to download from other instances"`
103105
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'."`

internal/config/helpers.gen.go

+50
Original file line numberDiff line numberDiff line change
@@ -1225,6 +1225,56 @@ func GetMediaEmojiRemoteMaxSize() bytesize.Size { return global.GetMediaEmojiRem
12251225
// SetMediaEmojiRemoteMaxSize safely sets the value for global configuration 'MediaEmojiRemoteMaxSize' field
12261226
func SetMediaEmojiRemoteMaxSize(v bytesize.Size) { global.SetMediaEmojiRemoteMaxSize(v) }
12271227

1228+
// GetMediaImageSizeHint safely fetches the Configuration value for state's 'MediaImageSizeHint' field
1229+
func (st *ConfigState) GetMediaImageSizeHint() (v bytesize.Size) {
1230+
st.mutex.RLock()
1231+
v = st.config.MediaImageSizeHint
1232+
st.mutex.RUnlock()
1233+
return
1234+
}
1235+
1236+
// SetMediaImageSizeHint safely sets the Configuration value for state's 'MediaImageSizeHint' field
1237+
func (st *ConfigState) SetMediaImageSizeHint(v bytesize.Size) {
1238+
st.mutex.Lock()
1239+
defer st.mutex.Unlock()
1240+
st.config.MediaImageSizeHint = v
1241+
st.reloadToViper()
1242+
}
1243+
1244+
// MediaImageSizeHintFlag returns the flag name for the 'MediaImageSizeHint' field
1245+
func MediaImageSizeHintFlag() string { return "media-image-size-hint" }
1246+
1247+
// GetMediaImageSizeHint safely fetches the value for global configuration 'MediaImageSizeHint' field
1248+
func GetMediaImageSizeHint() bytesize.Size { return global.GetMediaImageSizeHint() }
1249+
1250+
// SetMediaImageSizeHint safely sets the value for global configuration 'MediaImageSizeHint' field
1251+
func SetMediaImageSizeHint(v bytesize.Size) { global.SetMediaImageSizeHint(v) }
1252+
1253+
// GetMediaVideoSizeHint safely fetches the Configuration value for state's 'MediaVideoSizeHint' field
1254+
func (st *ConfigState) GetMediaVideoSizeHint() (v bytesize.Size) {
1255+
st.mutex.RLock()
1256+
v = st.config.MediaVideoSizeHint
1257+
st.mutex.RUnlock()
1258+
return
1259+
}
1260+
1261+
// SetMediaVideoSizeHint safely sets the Configuration value for state's 'MediaVideoSizeHint' field
1262+
func (st *ConfigState) SetMediaVideoSizeHint(v bytesize.Size) {
1263+
st.mutex.Lock()
1264+
defer st.mutex.Unlock()
1265+
st.config.MediaVideoSizeHint = v
1266+
st.reloadToViper()
1267+
}
1268+
1269+
// MediaVideoSizeHintFlag returns the flag name for the 'MediaVideoSizeHint' field
1270+
func MediaVideoSizeHintFlag() string { return "media-video-size-hint" }
1271+
1272+
// GetMediaVideoSizeHint safely fetches the value for global configuration 'MediaVideoSizeHint' field
1273+
func GetMediaVideoSizeHint() bytesize.Size { return global.GetMediaVideoSizeHint() }
1274+
1275+
// SetMediaVideoSizeHint safely sets the value for global configuration 'MediaVideoSizeHint' field
1276+
func SetMediaVideoSizeHint(v bytesize.Size) { global.SetMediaVideoSizeHint(v) }
1277+
12281278
// GetMediaLocalMaxSize safely fetches the Configuration value for state's 'MediaLocalMaxSize' field
12291279
func (st *ConfigState) GetMediaLocalMaxSize() (v bytesize.Size) {
12301280
st.mutex.RLock()

internal/typeutils/internaltofrontend.go

+45-20
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@
1818
package typeutils
1919

2020
import (
21+
"cmp"
2122
"context"
2223
"errors"
2324
"fmt"
25+
"math"
2426
"slices"
2527
"strconv"
2628
"strings"
@@ -42,16 +44,13 @@ import (
4244
)
4345

4446
const (
45-
instanceStatusesCharactersReservedPerURL = 25
46-
instanceMediaAttachmentsImageMatrixLimit = 16777216 // width * height
47-
instanceMediaAttachmentsVideoMatrixLimit = 16777216 // width * height
48-
instanceMediaAttachmentsVideoFrameRateLimit = 60
49-
instancePollsMinExpiration = 300 // seconds
50-
instancePollsMaxExpiration = 2629746 // seconds
51-
instanceAccountsMaxFeaturedTags = 10
52-
instanceAccountsMaxProfileFields = 6 // FIXME: https://github.com/superseriousbusiness/gotosocial/issues/1876
53-
instanceSourceURL = "https://github.com/superseriousbusiness/gotosocial"
54-
instanceMastodonVersion = "3.5.3"
47+
instanceStatusesCharactersReservedPerURL = 25
48+
instancePollsMinExpiration = 300 // seconds
49+
instancePollsMaxExpiration = 2629746 // seconds
50+
instanceAccountsMaxFeaturedTags = 10
51+
instanceAccountsMaxProfileFields = 6 // FIXME: https://github.com/superseriousbusiness/gotosocial/issues/1876
52+
instanceSourceURL = "https://github.com/superseriousbusiness/gotosocial"
53+
instanceMastodonVersion = "3.5.3"
5554
)
5655

5756
var instanceStatusesSupportedMimeTypes = []string{
@@ -1563,11 +1562,24 @@ func (c *Converter) InstanceToAPIV1Instance(ctx context.Context, i *gtsmodel.Ins
15631562
instance.Configuration.Statuses.CharactersReservedPerURL = instanceStatusesCharactersReservedPerURL
15641563
instance.Configuration.Statuses.SupportedMimeTypes = instanceStatusesSupportedMimeTypes
15651564
instance.Configuration.MediaAttachments.SupportedMimeTypes = media.SupportedMIMETypes
1566-
instance.Configuration.MediaAttachments.ImageSizeLimit = int(config.GetMediaRemoteMaxSize()) // #nosec G115 -- Already validated.
1567-
instance.Configuration.MediaAttachments.ImageMatrixLimit = instanceMediaAttachmentsImageMatrixLimit
1568-
instance.Configuration.MediaAttachments.VideoSizeLimit = int(config.GetMediaRemoteMaxSize()) // #nosec G115 -- Already validated.
1569-
instance.Configuration.MediaAttachments.VideoFrameRateLimit = instanceMediaAttachmentsVideoFrameRateLimit
1570-
instance.Configuration.MediaAttachments.VideoMatrixLimit = instanceMediaAttachmentsVideoMatrixLimit
1565+
1566+
// NOTE: we use the local max sizes here
1567+
// as it hints to apps like Tusky for image
1568+
// compression of locally uploaded media.
1569+
//
1570+
// TODO: return local / remote depending
1571+
// on authorized endpoint user (if any)?
1572+
localMax := config.GetMediaLocalMaxSize()
1573+
imageSz := cmp.Or(config.GetMediaImageSizeHint(), localMax)
1574+
videoSz := cmp.Or(config.GetMediaVideoSizeHint(), localMax)
1575+
instance.Configuration.MediaAttachments.ImageSizeLimit = int(imageSz) // #nosec G115 -- Already validated.
1576+
instance.Configuration.MediaAttachments.VideoSizeLimit = int(videoSz) // #nosec G115 -- Already validated.
1577+
1578+
// we don't actually set any limits on these. set to max possible.
1579+
instance.Configuration.MediaAttachments.ImageMatrixLimit = math.MaxInt
1580+
instance.Configuration.MediaAttachments.VideoFrameRateLimit = math.MaxInt
1581+
instance.Configuration.MediaAttachments.VideoMatrixLimit = math.MaxInt
1582+
15711583
instance.Configuration.Polls.MaxOptions = config.GetStatusesPollMaxOptions()
15721584
instance.Configuration.Polls.MaxCharactersPerOption = config.GetStatusesPollOptionMaxChars()
15731585
instance.Configuration.Polls.MinExpiration = instancePollsMinExpiration
@@ -1713,11 +1725,24 @@ func (c *Converter) InstanceToAPIV2Instance(ctx context.Context, i *gtsmodel.Ins
17131725
instance.Configuration.Statuses.CharactersReservedPerURL = instanceStatusesCharactersReservedPerURL
17141726
instance.Configuration.Statuses.SupportedMimeTypes = instanceStatusesSupportedMimeTypes
17151727
instance.Configuration.MediaAttachments.SupportedMimeTypes = media.SupportedMIMETypes
1716-
instance.Configuration.MediaAttachments.ImageSizeLimit = int(config.GetMediaRemoteMaxSize()) // #nosec G115 -- Already validated.
1717-
instance.Configuration.MediaAttachments.ImageMatrixLimit = instanceMediaAttachmentsImageMatrixLimit
1718-
instance.Configuration.MediaAttachments.VideoSizeLimit = int(config.GetMediaRemoteMaxSize()) // #nosec G115 -- Already validated.
1719-
instance.Configuration.MediaAttachments.VideoFrameRateLimit = instanceMediaAttachmentsVideoFrameRateLimit
1720-
instance.Configuration.MediaAttachments.VideoMatrixLimit = instanceMediaAttachmentsVideoMatrixLimit
1728+
1729+
// NOTE: we use the local max sizes here
1730+
// as it hints to apps like Tusky for image
1731+
// compression of locally uploaded media.
1732+
//
1733+
// TODO: return local / remote depending
1734+
// on authorized endpoint user (if any)?
1735+
localMax := config.GetMediaLocalMaxSize()
1736+
imageSz := cmp.Or(config.GetMediaImageSizeHint(), localMax)
1737+
videoSz := cmp.Or(config.GetMediaVideoSizeHint(), localMax)
1738+
instance.Configuration.MediaAttachments.ImageSizeLimit = int(imageSz) // #nosec G115 -- Already validated.
1739+
instance.Configuration.MediaAttachments.VideoSizeLimit = int(videoSz) // #nosec G115 -- Already validated.
1740+
1741+
// we don't actually set any limits on these. set to max possible.
1742+
instance.Configuration.MediaAttachments.ImageMatrixLimit = math.MaxInt
1743+
instance.Configuration.MediaAttachments.VideoFrameRateLimit = math.MaxInt
1744+
instance.Configuration.MediaAttachments.VideoMatrixLimit = math.MaxInt
1745+
17211746
instance.Configuration.Polls.MaxOptions = config.GetStatusesPollMaxOptions()
17221747
instance.Configuration.Polls.MaxCharactersPerOption = config.GetStatusesPollOptionMaxChars()
17231748
instance.Configuration.Polls.MinExpiration = instancePollsMinExpiration

internal/typeutils/internaltofrontend_test.go

+6-6
Original file line numberDiff line numberDiff line change
@@ -1968,10 +1968,10 @@ func (suite *InternalToFrontendTestSuite) TestInstanceV1ToFrontend() {
19681968
"video/x-matroska"
19691969
],
19701970
"image_size_limit": 41943040,
1971-
"image_matrix_limit": 16777216,
1971+
"image_matrix_limit": 9223372036854775807,
19721972
"video_size_limit": 41943040,
1973-
"video_frame_rate_limit": 60,
1974-
"video_matrix_limit": 16777216
1973+
"video_frame_rate_limit": 9223372036854775807,
1974+
"video_matrix_limit": 9223372036854775807
19751975
},
19761976
"polls": {
19771977
"max_options": 6,
@@ -2113,10 +2113,10 @@ func (suite *InternalToFrontendTestSuite) TestInstanceV2ToFrontend() {
21132113
"video/x-matroska"
21142114
],
21152115
"image_size_limit": 41943040,
2116-
"image_matrix_limit": 16777216,
2116+
"image_matrix_limit": 9223372036854775807,
21172117
"video_size_limit": 41943040,
2118-
"video_frame_rate_limit": 60,
2119-
"video_matrix_limit": 16777216
2118+
"video_frame_rate_limit": 9223372036854775807,
2119+
"video_matrix_limit": 9223372036854775807
21202120
},
21212121
"polls": {
21222122
"max_options": 6,

test/envparsing.sh

+4
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,11 @@ EXPECT=$(cat << "EOF"
129129
"media-emoji-local-max-size": 420,
130130
"media-emoji-remote-max-size": 420,
131131
"media-ffmpeg-pool-size": 8,
132+
"media-image-size-hint": 5242880,
132133
"media-local-max-size": 420,
133134
"media-remote-cache-days": 30,
134135
"media-remote-max-size": 420,
136+
"media-video-size-hint": 41943040,
135137
"metrics-auth-enabled": false,
136138
"metrics-auth-password": "",
137139
"metrics-auth-username": "",
@@ -244,12 +246,14 @@ GTS_ACCOUNTS_REGISTRATION_OPEN=true \
244246
GTS_ACCOUNTS_REASON_REQUIRED=false \
245247
GTS_MEDIA_DESCRIPTION_MIN_CHARS=69 \
246248
GTS_MEDIA_DESCRIPTION_MAX_CHARS=5000 \
249+
GTS_MEDIA_IMAGE_SIZE_HINT='5MiB' \
247250
GTS_MEDIA_LOCAL_MAX_SIZE=420 \
248251
GTS_MEDIA_REMOTE_MAX_SIZE=420 \
249252
GTS_MEDIA_REMOTE_CACHE_DAYS=30 \
250253
GTS_MEDIA_EMOJI_LOCAL_MAX_SIZE=420 \
251254
GTS_MEDIA_EMOJI_REMOTE_MAX_SIZE=420 \
252255
GTS_MEDIA_FFMPEG_POOL_SIZE=8 \
256+
GTS_MEDIA_VIDEO_SIZE_HINT='40MiB' \
253257
GTS_METRICS_AUTH_ENABLED=false \
254258
GTS_METRICS_ENABLED=false \
255259
GTS_STORAGE_BACKEND='local' \

0 commit comments

Comments
 (0)