Merge branch 'main' into domain_permission_subscriptions

This commit is contained in:
tobi 2024-11-05 12:04:22 +01:00
commit 372d040d90
23 changed files with 247 additions and 1423 deletions

View file

@ -18,6 +18,24 @@
# Default: 40MiB (41943040 bytes) # Default: 40MiB (41943040 bytes)
media-local-max-size: 40MiB 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. # Size. Max size in bytes of media to download from other instances.
# #
# Lowering this limit may cause your instance not to fetch post media. # Lowering this limit may cause your instance not to fetch post media.

View file

@ -471,6 +471,24 @@ accounts-custom-css-length: 10000
# Default: 40MiB (41943040 bytes) # Default: 40MiB (41943040 bytes)
media-local-max-size: 40MiB 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. # Size. Max size in bytes of media to download from other instances.
# #
# Lowering this limit may cause your instance not to fetch post media. # Lowering this limit may cause your instance not to fetch post media.

1
go.mod
View file

@ -17,7 +17,6 @@ require (
codeberg.org/gruf/go-kv v1.6.5 codeberg.org/gruf/go-kv v1.6.5
codeberg.org/gruf/go-list v0.0.0-20240425093752-494db03d641f codeberg.org/gruf/go-list v0.0.0-20240425093752-494db03d641f
codeberg.org/gruf/go-mempool v0.0.0-20240507125005-cef10d64a760 codeberg.org/gruf/go-mempool v0.0.0-20240507125005-cef10d64a760
codeberg.org/gruf/go-mimetypes v1.2.0
codeberg.org/gruf/go-mutexes v1.5.1 codeberg.org/gruf/go-mutexes v1.5.1
codeberg.org/gruf/go-runners v1.6.3 codeberg.org/gruf/go-runners v1.6.3
codeberg.org/gruf/go-sched v1.2.4 codeberg.org/gruf/go-sched v1.2.4

2
go.sum generated
View file

@ -62,8 +62,6 @@ codeberg.org/gruf/go-maps v1.0.4 h1:K+Ww4vvR3TZqm5jqrKVirmguZwa3v1VUvmig2SE8uxY=
codeberg.org/gruf/go-maps v1.0.4/go.mod h1:ASX7osM7kFwt5O8GfGflcFjrwYGD8eIuRLl/oMjhEi8= codeberg.org/gruf/go-maps v1.0.4/go.mod h1:ASX7osM7kFwt5O8GfGflcFjrwYGD8eIuRLl/oMjhEi8=
codeberg.org/gruf/go-mempool v0.0.0-20240507125005-cef10d64a760 h1:m2/UCRXhjDwAg4vyji6iKCpomKw6P4PmBOUi5DvAMH4= codeberg.org/gruf/go-mempool v0.0.0-20240507125005-cef10d64a760 h1:m2/UCRXhjDwAg4vyji6iKCpomKw6P4PmBOUi5DvAMH4=
codeberg.org/gruf/go-mempool v0.0.0-20240507125005-cef10d64a760/go.mod h1:E3RcaCFNq4zXpvaJb8lfpPqdUAmSkP5F1VmMiEUYTEk= codeberg.org/gruf/go-mempool v0.0.0-20240507125005-cef10d64a760/go.mod h1:E3RcaCFNq4zXpvaJb8lfpPqdUAmSkP5F1VmMiEUYTEk=
codeberg.org/gruf/go-mimetypes v1.2.0 h1:3rZGXY/SkNYbamiddWXs2gETXIBkGIeWYnbWpp2OEbc=
codeberg.org/gruf/go-mimetypes v1.2.0/go.mod h1:YiUWRj/nAdJQc+UFRvcsL6xXZsbc6b6Ic739ycEO8Yg=
codeberg.org/gruf/go-mutexes v1.5.1 h1:xICU0WXhWr6wf+Iror4eE3xT+xnXNPrO6o77D/G6QuY= codeberg.org/gruf/go-mutexes v1.5.1 h1:xICU0WXhWr6wf+Iror4eE3xT+xnXNPrO6o77D/G6QuY=
codeberg.org/gruf/go-mutexes v1.5.1/go.mod h1:rPEqQ/y6CmGITaZ3GPTMQVsoZAOzbsAHyIaLsJcOqVE= codeberg.org/gruf/go-mutexes v1.5.1/go.mod h1:rPEqQ/y6CmGITaZ3GPTMQVsoZAOzbsAHyIaLsJcOqVE=
codeberg.org/gruf/go-runners v1.6.3 h1:To/AX7eTrWuXrTkA3RA01YTP5zha1VZ68LQ+0D4RY7E= codeberg.org/gruf/go-runners v1.6.3 h1:To/AX7eTrWuXrTkA3RA01YTP5zha1VZ68LQ+0D4RY7E=

View file

@ -120,7 +120,7 @@ func (suite *InstancePatchTestSuite) TestInstancePatch1() {
"image/apng", "image/apng",
"audio/ogg", "audio/ogg",
"video/ogg", "video/ogg",
"audio/x-m4a", "audio/mp4",
"video/mp4", "video/mp4",
"video/quicktime", "video/quicktime",
"audio/x-ms-wma", "audio/x-ms-wma",
@ -130,10 +130,10 @@ func (suite *InstancePatchTestSuite) TestInstancePatch1() {
"video/x-matroska" "video/x-matroska"
], ],
"image_size_limit": 41943040, "image_size_limit": 41943040,
"image_matrix_limit": 16777216, "image_matrix_limit": 9223372036854775807,
"video_size_limit": 41943040, "video_size_limit": 41943040,
"video_frame_rate_limit": 60, "video_frame_rate_limit": 9223372036854775807,
"video_matrix_limit": 16777216 "video_matrix_limit": 9223372036854775807
}, },
"polls": { "polls": {
"max_options": 6, "max_options": 6,
@ -261,7 +261,7 @@ func (suite *InstancePatchTestSuite) TestInstancePatch2() {
"image/apng", "image/apng",
"audio/ogg", "audio/ogg",
"video/ogg", "video/ogg",
"audio/x-m4a", "audio/mp4",
"video/mp4", "video/mp4",
"video/quicktime", "video/quicktime",
"audio/x-ms-wma", "audio/x-ms-wma",
@ -271,10 +271,10 @@ func (suite *InstancePatchTestSuite) TestInstancePatch2() {
"video/x-matroska" "video/x-matroska"
], ],
"image_size_limit": 41943040, "image_size_limit": 41943040,
"image_matrix_limit": 16777216, "image_matrix_limit": 9223372036854775807,
"video_size_limit": 41943040, "video_size_limit": 41943040,
"video_frame_rate_limit": 60, "video_frame_rate_limit": 9223372036854775807,
"video_matrix_limit": 16777216 "video_matrix_limit": 9223372036854775807
}, },
"polls": { "polls": {
"max_options": 6, "max_options": 6,
@ -402,7 +402,7 @@ func (suite *InstancePatchTestSuite) TestInstancePatch3() {
"image/apng", "image/apng",
"audio/ogg", "audio/ogg",
"video/ogg", "video/ogg",
"audio/x-m4a", "audio/mp4",
"video/mp4", "video/mp4",
"video/quicktime", "video/quicktime",
"audio/x-ms-wma", "audio/x-ms-wma",
@ -412,10 +412,10 @@ func (suite *InstancePatchTestSuite) TestInstancePatch3() {
"video/x-matroska" "video/x-matroska"
], ],
"image_size_limit": 41943040, "image_size_limit": 41943040,
"image_matrix_limit": 16777216, "image_matrix_limit": 9223372036854775807,
"video_size_limit": 41943040, "video_size_limit": 41943040,
"video_frame_rate_limit": 60, "video_frame_rate_limit": 9223372036854775807,
"video_matrix_limit": 16777216 "video_matrix_limit": 9223372036854775807
}, },
"polls": { "polls": {
"max_options": 6, "max_options": 6,
@ -594,7 +594,7 @@ func (suite *InstancePatchTestSuite) TestInstancePatch6() {
"image/apng", "image/apng",
"audio/ogg", "audio/ogg",
"video/ogg", "video/ogg",
"audio/x-m4a", "audio/mp4",
"video/mp4", "video/mp4",
"video/quicktime", "video/quicktime",
"audio/x-ms-wma", "audio/x-ms-wma",
@ -604,10 +604,10 @@ func (suite *InstancePatchTestSuite) TestInstancePatch6() {
"video/x-matroska" "video/x-matroska"
], ],
"image_size_limit": 41943040, "image_size_limit": 41943040,
"image_matrix_limit": 16777216, "image_matrix_limit": 9223372036854775807,
"video_size_limit": 41943040, "video_size_limit": 41943040,
"video_frame_rate_limit": 60, "video_frame_rate_limit": 9223372036854775807,
"video_matrix_limit": 16777216 "video_matrix_limit": 9223372036854775807
}, },
"polls": { "polls": {
"max_options": 6, "max_options": 6,
@ -757,7 +757,7 @@ func (suite *InstancePatchTestSuite) TestInstancePatch8() {
"image/apng", "image/apng",
"audio/ogg", "audio/ogg",
"video/ogg", "video/ogg",
"audio/x-m4a", "audio/mp4",
"video/mp4", "video/mp4",
"video/quicktime", "video/quicktime",
"audio/x-ms-wma", "audio/x-ms-wma",
@ -767,10 +767,10 @@ func (suite *InstancePatchTestSuite) TestInstancePatch8() {
"video/x-matroska" "video/x-matroska"
], ],
"image_size_limit": 41943040, "image_size_limit": 41943040,
"image_matrix_limit": 16777216, "image_matrix_limit": 9223372036854775807,
"video_size_limit": 41943040, "video_size_limit": 41943040,
"video_frame_rate_limit": 60, "video_frame_rate_limit": 9223372036854775807,
"video_matrix_limit": 16777216 "video_matrix_limit": 9223372036854775807
}, },
"polls": { "polls": {
"max_options": 6, "max_options": 6,
@ -939,7 +939,7 @@ func (suite *InstancePatchTestSuite) TestInstancePatch9() {
"image/apng", "image/apng",
"audio/ogg", "audio/ogg",
"video/ogg", "video/ogg",
"audio/x-m4a", "audio/mp4",
"video/mp4", "video/mp4",
"video/quicktime", "video/quicktime",
"audio/x-ms-wma", "audio/x-ms-wma",
@ -949,10 +949,10 @@ func (suite *InstancePatchTestSuite) TestInstancePatch9() {
"video/x-matroska" "video/x-matroska"
], ],
"image_size_limit": 41943040, "image_size_limit": 41943040,
"image_matrix_limit": 16777216, "image_matrix_limit": 9223372036854775807,
"video_size_limit": 41943040, "video_size_limit": 41943040,
"video_frame_rate_limit": 60, "video_frame_rate_limit": 9223372036854775807,
"video_matrix_limit": 16777216 "video_matrix_limit": 9223372036854775807
}, },
"polls": { "polls": {
"max_options": 6, "max_options": 6,

View file

@ -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."` 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."` 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."` 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"` 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"` 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'."` 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'."`

View file

@ -1225,6 +1225,56 @@ func GetMediaEmojiRemoteMaxSize() bytesize.Size { return global.GetMediaEmojiRem
// SetMediaEmojiRemoteMaxSize safely sets the value for global configuration 'MediaEmojiRemoteMaxSize' field // SetMediaEmojiRemoteMaxSize safely sets the value for global configuration 'MediaEmojiRemoteMaxSize' field
func SetMediaEmojiRemoteMaxSize(v bytesize.Size) { global.SetMediaEmojiRemoteMaxSize(v) } 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 // GetMediaLocalMaxSize safely fetches the Configuration value for state's 'MediaLocalMaxSize' field
func (st *ConfigState) GetMediaLocalMaxSize() (v bytesize.Size) { func (st *ConfigState) GetMediaLocalMaxSize() (v bytesize.Size) {
st.mutex.RLock() st.mutex.RLock()

View file

@ -323,14 +323,14 @@ type videoStream struct {
// //
// Note the checks for (len(res.video) > 0) may catch some audio files with embedded // Note the checks for (len(res.video) > 0) may catch some audio files with embedded
// album art as video, but i blame that on the hellscape that is media filetypes. // album art as video, but i blame that on the hellscape that is media filetypes.
// func (res *result) GetFileType() (gtsmodel.FileType, string, string) {
// TODO: we can update this code to also return a mimetype and avoid later parsing!
func (res *result) GetFileType() (gtsmodel.FileType, string) {
switch res.format { switch res.format {
case "mpeg": case "mpeg":
return gtsmodel.FileTypeVideo, "mpeg" return gtsmodel.FileTypeVideo,
"video/mpeg", "mpeg"
case "mjpeg": case "mjpeg":
return gtsmodel.FileTypeVideo, "mjpeg" return gtsmodel.FileTypeVideo,
"video/x-motion-jpeg", "mjpeg"
case "mov,mp4,m4a,3gp,3g2,mj2": case "mov,mp4,m4a,3gp,3g2,mj2":
switch { switch {
case len(res.video) > 0: case len(res.video) > 0:
@ -338,55 +338,70 @@ func (res *result) GetFileType() (gtsmodel.FileType, string) {
res.duration <= 30 { res.duration <= 30 {
// Short, soundless // Short, soundless
// video file aka gifv. // video file aka gifv.
return gtsmodel.FileTypeGifv, "mp4" return gtsmodel.FileTypeGifv,
"video/mp4", "mp4"
} else { } else {
// Video file (with or without audio). // Video file (with or without audio).
return gtsmodel.FileTypeVideo, "mp4" return gtsmodel.FileTypeVideo,
"video/mp4", "mp4"
} }
case len(res.audio) > 0 && case len(res.audio) > 0 &&
res.audio[0].codec == "aac": res.audio[0].codec == "aac":
// m4a only supports [aac] audio. // m4a only supports [aac] audio.
return gtsmodel.FileTypeAudio, "m4a" return gtsmodel.FileTypeAudio,
"audio/mp4", "m4a"
} }
case "apng": case "apng":
return gtsmodel.FileTypeImage, "apng" return gtsmodel.FileTypeImage,
"image/apng", "apng"
case "png_pipe": case "png_pipe":
return gtsmodel.FileTypeImage, "png" return gtsmodel.FileTypeImage,
"image/png", "png"
case "image2", "image2pipe", "jpeg_pipe": case "image2", "image2pipe", "jpeg_pipe":
return gtsmodel.FileTypeImage, "jpeg" return gtsmodel.FileTypeImage,
"image/jpeg", "jpeg"
case "webp", "webp_pipe": case "webp", "webp_pipe":
return gtsmodel.FileTypeImage, "webp" return gtsmodel.FileTypeImage,
"image/webp", "webp"
case "gif": case "gif":
return gtsmodel.FileTypeImage, "gif" return gtsmodel.FileTypeImage,
"image/gif", "gif"
case "mp3": case "mp3":
if len(res.audio) > 0 { if len(res.audio) > 0 {
switch res.audio[0].codec { switch res.audio[0].codec {
case "mp2": case "mp2":
return gtsmodel.FileTypeAudio, "mp2" return gtsmodel.FileTypeAudio,
"audio/mp2", "mp2"
case "mp3": case "mp3":
return gtsmodel.FileTypeAudio, "mp3" return gtsmodel.FileTypeAudio,
"audio/mp3", "mp3"
} }
} }
case "asf": case "asf":
switch { switch {
case len(res.video) > 0: case len(res.video) > 0:
return gtsmodel.FileTypeVideo, "wmv" return gtsmodel.FileTypeVideo,
"video/x-ms-wmv", "wmv"
case len(res.audio) > 0: case len(res.audio) > 0:
return gtsmodel.FileTypeAudio, "wma" return gtsmodel.FileTypeAudio,
"audio/x-ms-wma", "wma"
} }
case "ogg": case "ogg":
if len(res.video) > 0 { if len(res.video) > 0 {
switch res.video[0].codec { switch res.video[0].codec {
case "theora", "dirac": // daala, tarkin case "theora", "dirac": // daala, tarkin
return gtsmodel.FileTypeVideo, "ogv" return gtsmodel.FileTypeVideo,
"video/ogg", "ogv"
} }
} }
if len(res.audio) > 0 { if len(res.audio) > 0 {
switch res.audio[0].codec { switch res.audio[0].codec {
case "opus", "libopus": case "opus", "libopus":
return gtsmodel.FileTypeAudio, "opus" return gtsmodel.FileTypeAudio,
"audio/opus", "opus"
default: default:
return gtsmodel.FileTypeAudio, "ogg" return gtsmodel.FileTypeAudio,
"audio/ogg", "ogg"
} }
} }
case "matroska,webm": case "matroska,webm":
@ -411,21 +426,27 @@ func (res *result) GetFileType() (gtsmodel.FileType, string) {
} }
if isWebm { if isWebm {
// Check for valid webm codec config. // Check valid webm codec config.
return gtsmodel.FileTypeVideo, "webm" return gtsmodel.FileTypeVideo,
"video/webm", "webm"
} }
// All else falls under generic mkv. // All else falls under generic mkv.
return gtsmodel.FileTypeVideo, "mkv" return gtsmodel.FileTypeVideo,
"video/x-matroska", "mkv"
case len(res.audio) > 0: case len(res.audio) > 0:
return gtsmodel.FileTypeAudio, "mka" return gtsmodel.FileTypeAudio,
"audio/x-matroska", "mka"
} }
case "avi": case "avi":
return gtsmodel.FileTypeVideo, "avi" return gtsmodel.FileTypeVideo,
"video/x-msvideo", "avi"
case "flac": case "flac":
return gtsmodel.FileTypeAudio, "flac" return gtsmodel.FileTypeAudio,
"audio/flac", "flac"
} }
return gtsmodel.FileTypeUnknown, res.format return gtsmodel.FileTypeUnknown,
"", res.format
} }
// ImageMeta extracts image metadata contained within ffprobe'd media result streams. // ImageMeta extracts image metadata contained within ffprobe'd media result streams.

View file

@ -40,7 +40,7 @@ func InitFfprobe(ctx context.Context, max int) error {
// Ffprobe runs the given arguments with an instance of ffprobe. // Ffprobe runs the given arguments with an instance of ffprobe.
func Ffprobe(ctx context.Context, args Args) (uint32, error) { func Ffprobe(ctx context.Context, args Args) (uint32, error) {
return ffmpegRunner.Run(ctx, func() (uint32, error) { return ffprobeRunner.Run(ctx, func() (uint32, error) {
return wasm.Run(ctx, runtime, ffprobe, args) return wasm.Run(ctx, runtime, ffprobe, args)
}) })
} }

View file

@ -56,7 +56,7 @@
"video/ogg", // .ogv "video/ogg", // .ogv
// mpeg4 types // mpeg4 types
"audio/x-m4a", // .m4a "audio/mp4", // .m4a
"video/mp4", // .mp4 "video/mp4", // .mp4
"video/quicktime", // .mov "video/quicktime", // .mov

View file

@ -664,7 +664,7 @@ func (suite *ManagerTestSuite) TestOpusProcess() {
Duration: util.Ptr(float32(122.10006)), Duration: util.Ptr(float32(122.10006)),
Bitrate: util.Ptr(uint64(116426)), Bitrate: util.Ptr(uint64(116426)),
}, attachment.FileMeta.Original) }, attachment.FileMeta.Original)
suite.Equal("audio/ogg", attachment.File.ContentType) suite.Equal("audio/opus", attachment.File.ContentType)
suite.Equal(1776956, attachment.File.FileSize) suite.Equal(1776956, attachment.File.FileSize)
suite.Empty(attachment.Blurhash) suite.Empty(attachment.Blurhash)

View file

@ -163,9 +163,10 @@ func (p *ProcessingEmoji) store(ctx context.Context) error {
} }
var ext string var ext string
var fileType gtsmodel.FileType
// Get type from ffprobe format data. // Get abstract file type, mimetype and ext from ffprobe data.
fileType, ext := result.GetFileType() fileType, p.emoji.ImageContentType, ext = result.GetFileType()
if fileType != gtsmodel.FileTypeImage { if fileType != gtsmodel.FileTypeImage {
return gtserror.Newf("unsupported emoji filetype: %s (%s)", fileType, ext) return gtserror.Newf("unsupported emoji filetype: %s (%s)", fileType, ext)
} }
@ -216,10 +217,6 @@ func (p *ProcessingEmoji) store(ctx context.Context) error {
"png", "png",
) )
// Get mimetype for the file container
// type, falling back to generic data.
p.emoji.ImageContentType = getMimeType(ext)
// Set the known emoji static content type. // Set the known emoji static content type.
p.emoji.ImageStaticContentType = "image/png" p.emoji.ImageStaticContentType = "image/png"

View file

@ -186,8 +186,8 @@ func (p *ProcessingMedia) store(ctx context.Context) error {
p.media.FileMeta.Original.Duration = util.PtrIf(float32(result.duration)) p.media.FileMeta.Original.Duration = util.PtrIf(float32(result.duration))
p.media.FileMeta.Original.Bitrate = util.PtrIf(result.bitrate) p.media.FileMeta.Original.Bitrate = util.PtrIf(result.bitrate)
// Set media type from ffprobe format data. // Set generic media type and mimetype from ffprobe format data.
p.media.Type, ext = result.GetFileType() p.media.Type, p.media.File.ContentType, ext = result.GetFileType()
// Add file extension to path. // Add file extension to path.
newpath := temppath + "." + ext newpath := temppath + "." + ext
@ -236,10 +236,10 @@ func (p *ProcessingMedia) store(ctx context.Context) error {
// Determine if blurhash needs generating. // Determine if blurhash needs generating.
needBlurhash := (p.media.Blurhash == "") needBlurhash := (p.media.Blurhash == "")
var newBlurhash string var newBlurhash, mimeType string
// Generate thumbnail, and new blurhash if need from media. // Generate thumbnail, and new blurhash if needed from temp media.
thumbpath, newBlurhash, err = generateThumb(ctx, temppath, thumbpath, mimeType, newBlurhash, err = generateThumb(ctx, temppath,
thumbWidth, thumbWidth,
thumbHeight, thumbHeight,
result.orientation, result.orientation,
@ -250,6 +250,9 @@ func (p *ProcessingMedia) store(ctx context.Context) error {
return gtserror.Newf("error generating image thumb: %w", err) return gtserror.Newf("error generating image thumb: %w", err)
} }
// Set generated thumbnail's mimetype.
p.media.Thumbnail.ContentType = mimeType
if needBlurhash { if needBlurhash {
// Set newly determined blurhash. // Set newly determined blurhash.
p.media.Blurhash = newBlurhash p.media.Blurhash = newBlurhash
@ -265,10 +268,6 @@ func (p *ProcessingMedia) store(ctx context.Context) error {
ext, ext,
) )
// Get mimetype for the file container
// type, falling back to generic data.
p.media.File.ContentType = getMimeType(ext)
// Copy temporary file into storage at path. // Copy temporary file into storage at path.
filesz, err := p.mgr.state.Storage.PutFile(ctx, filesz, err := p.mgr.state.Storage.PutFile(ctx,
p.media.File.Path, p.media.File.Path,
@ -295,9 +294,6 @@ func (p *ProcessingMedia) store(ctx context.Context) error {
thumbExt, thumbExt,
) )
// Determine thumbnail content-type from thumb ext.
p.media.Thumbnail.ContentType = getMimeType(thumbExt)
// Copy thumbnail file into storage at path. // Copy thumbnail file into storage at path.
thumbsz, err := p.mgr.state.Storage.PutFile(ctx, thumbsz, err := p.mgr.state.Storage.PutFile(ctx,
p.media.Thumbnail.Path, p.media.Thumbnail.Path,

View file

@ -84,17 +84,21 @@ func generateThumb(
needBlurhash bool, needBlurhash bool,
) ( ) (
outpath string, outpath string,
mimeType string,
blurhash string, blurhash string,
err error, err error,
) { ) {
var ext string var ext string
// Default type is webp.
mimeType = "image/webp"
// Generate thumb output path REPLACING extension. // Generate thumb output path REPLACING extension.
if i := strings.IndexByte(filepath, '.'); i != -1 { if i := strings.IndexByte(filepath, '.'); i != -1 {
outpath = filepath[:i] + "_thumb.webp" outpath = filepath[:i] + "_thumb.webp"
ext = filepath[i+1:] // old extension ext = filepath[i+1:] // old extension
} else { } else {
return "", "", gtserror.New("input file missing extension") return "", "", "", gtserror.New("input file missing extension")
} }
// Check for the few media types we // Check for the few media types we
@ -106,6 +110,7 @@ func generateThumb(
// Replace the "webp" with "jpeg", as we'll // Replace the "webp" with "jpeg", as we'll
// use our native Go thumbnailing generation. // use our native Go thumbnailing generation.
outpath = outpath[:len(outpath)-4] + "jpeg" outpath = outpath[:len(outpath)-4] + "jpeg"
mimeType = "image/jpeg"
log.Debug(ctx, "generating thumb from jpeg") log.Debug(ctx, "generating thumb from jpeg")
blurhash, err := generateNativeThumb( blurhash, err := generateNativeThumb(
@ -117,7 +122,7 @@ func generateThumb(
jpeg.Decode, jpeg.Decode,
needBlurhash, needBlurhash,
) )
return outpath, blurhash, err return outpath, mimeType, blurhash, err
// We specifically only allow generating native // We specifically only allow generating native
// thumbnails from gif IF it doesn't contain an // thumbnails from gif IF it doesn't contain an
@ -128,6 +133,7 @@ func generateThumb(
// Replace the "webp" with "jpeg", as we'll // Replace the "webp" with "jpeg", as we'll
// use our native Go thumbnailing generation. // use our native Go thumbnailing generation.
outpath = outpath[:len(outpath)-4] + "jpeg" outpath = outpath[:len(outpath)-4] + "jpeg"
mimeType = "image/jpeg"
log.Debug(ctx, "generating thumb from gif") log.Debug(ctx, "generating thumb from gif")
blurhash, err := generateNativeThumb( blurhash, err := generateNativeThumb(
@ -139,7 +145,7 @@ func generateThumb(
gif.Decode, gif.Decode,
needBlurhash, needBlurhash,
) )
return outpath, blurhash, err return outpath, mimeType, blurhash, err
// We specifically only allow generating native // We specifically only allow generating native
// thumbnails from png IF it doesn't contain an // thumbnails from png IF it doesn't contain an
@ -150,6 +156,7 @@ func generateThumb(
// Replace the "webp" with "jpeg", as we'll // Replace the "webp" with "jpeg", as we'll
// use our native Go thumbnailing generation. // use our native Go thumbnailing generation.
outpath = outpath[:len(outpath)-4] + "jpeg" outpath = outpath[:len(outpath)-4] + "jpeg"
mimeType = "image/jpeg"
log.Debug(ctx, "generating thumb from png") log.Debug(ctx, "generating thumb from png")
blurhash, err := generateNativeThumb( blurhash, err := generateNativeThumb(
@ -161,7 +168,7 @@ func generateThumb(
png.Decode, png.Decode,
needBlurhash, needBlurhash,
) )
return outpath, blurhash, err return outpath, mimeType, blurhash, err
// We specifically only allow generating native // We specifically only allow generating native
// thumbnails from webp IF it doesn't contain an // thumbnails from webp IF it doesn't contain an
@ -172,6 +179,7 @@ func generateThumb(
// Replace the "webp" with "jpeg", as we'll // Replace the "webp" with "jpeg", as we'll
// use our native Go thumbnailing generation. // use our native Go thumbnailing generation.
outpath = outpath[:len(outpath)-4] + "jpeg" outpath = outpath[:len(outpath)-4] + "jpeg"
mimeType = "image/jpeg"
log.Debug(ctx, "generating thumb from webp") log.Debug(ctx, "generating thumb from webp")
blurhash, err := generateNativeThumb( blurhash, err := generateNativeThumb(
@ -183,7 +191,7 @@ func generateThumb(
webp.Decode, webp.Decode,
needBlurhash, needBlurhash,
) )
return outpath, blurhash, err return outpath, mimeType, blurhash, err
} }
// The fallback for thumbnail generation, which // The fallback for thumbnail generation, which
@ -196,18 +204,18 @@ func generateThumb(
height, height,
pixfmt, pixfmt,
); err != nil { ); err != nil {
return outpath, "", err return outpath, "", "", err
} }
if needBlurhash { if needBlurhash {
// Generate new blurhash from webp output thumb. // Generate new blurhash from webp output thumb.
blurhash, err = generateWebpBlurhash(outpath) blurhash, err = generateWebpBlurhash(outpath)
if err != nil { if err != nil {
return outpath, "", gtserror.Newf("error generating blurhash: %w", err) return outpath, "", "", gtserror.Newf("error generating blurhash: %w", err)
} }
} }
return outpath, blurhash, err return outpath, mimeType, blurhash, nil
} }
// generateNativeThumb generates a thumbnail // generateNativeThumb generates a thumbnail

View file

@ -18,7 +18,6 @@
package media package media
import ( import (
"cmp"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@ -28,7 +27,6 @@
"codeberg.org/gruf/go-bytesize" "codeberg.org/gruf/go-bytesize"
"codeberg.org/gruf/go-iotools" "codeberg.org/gruf/go-iotools"
"codeberg.org/gruf/go-mimetypes"
"github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtserror"
) )
@ -87,12 +85,6 @@ func getExtension(path string) string {
return "" return ""
} }
// getMimeType returns a suitable mimetype for file extension.
func getMimeType(ext string) string {
const defaultType = "application/octet-stream"
return cmp.Or(mimetypes.MimeTypes[ext], defaultType)
}
// drainToTmp drains data from given reader into a new temp file // drainToTmp drains data from given reader into a new temp file
// and closes it, returning the path of the resulting temp file. // and closes it, returning the path of the resulting temp file.
// //

View file

@ -18,9 +18,11 @@
package typeutils package typeutils
import ( import (
"cmp"
"context" "context"
"errors" "errors"
"fmt" "fmt"
"math"
"slices" "slices"
"strconv" "strconv"
"strings" "strings"
@ -43,9 +45,6 @@
const ( const (
instanceStatusesCharactersReservedPerURL = 25 instanceStatusesCharactersReservedPerURL = 25
instanceMediaAttachmentsImageMatrixLimit = 16777216 // width * height
instanceMediaAttachmentsVideoMatrixLimit = 16777216 // width * height
instanceMediaAttachmentsVideoFrameRateLimit = 60
instancePollsMinExpiration = 300 // seconds instancePollsMinExpiration = 300 // seconds
instancePollsMaxExpiration = 2629746 // seconds instancePollsMaxExpiration = 2629746 // seconds
instanceAccountsMaxFeaturedTags = 10 instanceAccountsMaxFeaturedTags = 10
@ -1563,11 +1562,24 @@ func (c *Converter) InstanceToAPIV1Instance(ctx context.Context, i *gtsmodel.Ins
instance.Configuration.Statuses.CharactersReservedPerURL = instanceStatusesCharactersReservedPerURL instance.Configuration.Statuses.CharactersReservedPerURL = instanceStatusesCharactersReservedPerURL
instance.Configuration.Statuses.SupportedMimeTypes = instanceStatusesSupportedMimeTypes instance.Configuration.Statuses.SupportedMimeTypes = instanceStatusesSupportedMimeTypes
instance.Configuration.MediaAttachments.SupportedMimeTypes = media.SupportedMIMETypes instance.Configuration.MediaAttachments.SupportedMimeTypes = media.SupportedMIMETypes
instance.Configuration.MediaAttachments.ImageSizeLimit = int(config.GetMediaRemoteMaxSize()) // #nosec G115 -- Already validated.
instance.Configuration.MediaAttachments.ImageMatrixLimit = instanceMediaAttachmentsImageMatrixLimit // NOTE: we use the local max sizes here
instance.Configuration.MediaAttachments.VideoSizeLimit = int(config.GetMediaRemoteMaxSize()) // #nosec G115 -- Already validated. // as it hints to apps like Tusky for image
instance.Configuration.MediaAttachments.VideoFrameRateLimit = instanceMediaAttachmentsVideoFrameRateLimit // compression of locally uploaded media.
instance.Configuration.MediaAttachments.VideoMatrixLimit = instanceMediaAttachmentsVideoMatrixLimit //
// 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.MaxOptions = config.GetStatusesPollMaxOptions()
instance.Configuration.Polls.MaxCharactersPerOption = config.GetStatusesPollOptionMaxChars() instance.Configuration.Polls.MaxCharactersPerOption = config.GetStatusesPollOptionMaxChars()
instance.Configuration.Polls.MinExpiration = instancePollsMinExpiration 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.CharactersReservedPerURL = instanceStatusesCharactersReservedPerURL
instance.Configuration.Statuses.SupportedMimeTypes = instanceStatusesSupportedMimeTypes instance.Configuration.Statuses.SupportedMimeTypes = instanceStatusesSupportedMimeTypes
instance.Configuration.MediaAttachments.SupportedMimeTypes = media.SupportedMIMETypes instance.Configuration.MediaAttachments.SupportedMimeTypes = media.SupportedMIMETypes
instance.Configuration.MediaAttachments.ImageSizeLimit = int(config.GetMediaRemoteMaxSize()) // #nosec G115 -- Already validated.
instance.Configuration.MediaAttachments.ImageMatrixLimit = instanceMediaAttachmentsImageMatrixLimit // NOTE: we use the local max sizes here
instance.Configuration.MediaAttachments.VideoSizeLimit = int(config.GetMediaRemoteMaxSize()) // #nosec G115 -- Already validated. // as it hints to apps like Tusky for image
instance.Configuration.MediaAttachments.VideoFrameRateLimit = instanceMediaAttachmentsVideoFrameRateLimit // compression of locally uploaded media.
instance.Configuration.MediaAttachments.VideoMatrixLimit = instanceMediaAttachmentsVideoMatrixLimit //
// 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.MaxOptions = config.GetStatusesPollMaxOptions()
instance.Configuration.Polls.MaxCharactersPerOption = config.GetStatusesPollOptionMaxChars() instance.Configuration.Polls.MaxCharactersPerOption = config.GetStatusesPollOptionMaxChars()
instance.Configuration.Polls.MinExpiration = instancePollsMinExpiration instance.Configuration.Polls.MinExpiration = instancePollsMinExpiration

View file

@ -1958,7 +1958,7 @@ func (suite *InternalToFrontendTestSuite) TestInstanceV1ToFrontend() {
"image/apng", "image/apng",
"audio/ogg", "audio/ogg",
"video/ogg", "video/ogg",
"audio/x-m4a", "audio/mp4",
"video/mp4", "video/mp4",
"video/quicktime", "video/quicktime",
"audio/x-ms-wma", "audio/x-ms-wma",
@ -1968,10 +1968,10 @@ func (suite *InternalToFrontendTestSuite) TestInstanceV1ToFrontend() {
"video/x-matroska" "video/x-matroska"
], ],
"image_size_limit": 41943040, "image_size_limit": 41943040,
"image_matrix_limit": 16777216, "image_matrix_limit": 9223372036854775807,
"video_size_limit": 41943040, "video_size_limit": 41943040,
"video_frame_rate_limit": 60, "video_frame_rate_limit": 9223372036854775807,
"video_matrix_limit": 16777216 "video_matrix_limit": 9223372036854775807
}, },
"polls": { "polls": {
"max_options": 6, "max_options": 6,
@ -2103,7 +2103,7 @@ func (suite *InternalToFrontendTestSuite) TestInstanceV2ToFrontend() {
"image/apng", "image/apng",
"audio/ogg", "audio/ogg",
"video/ogg", "video/ogg",
"audio/x-m4a", "audio/mp4",
"video/mp4", "video/mp4",
"video/quicktime", "video/quicktime",
"audio/x-ms-wma", "audio/x-ms-wma",
@ -2113,10 +2113,10 @@ func (suite *InternalToFrontendTestSuite) TestInstanceV2ToFrontend() {
"video/x-matroska" "video/x-matroska"
], ],
"image_size_limit": 41943040, "image_size_limit": 41943040,
"image_matrix_limit": 16777216, "image_matrix_limit": 9223372036854775807,
"video_size_limit": 41943040, "video_size_limit": 41943040,
"video_frame_rate_limit": 60, "video_frame_rate_limit": 9223372036854775807,
"video_matrix_limit": 16777216 "video_matrix_limit": 9223372036854775807
}, },
"polls": { "polls": {
"max_options": 6, "max_options": 6,

View file

@ -129,9 +129,11 @@ EXPECT=$(cat << "EOF"
"media-emoji-local-max-size": 420, "media-emoji-local-max-size": 420,
"media-emoji-remote-max-size": 420, "media-emoji-remote-max-size": 420,
"media-ffmpeg-pool-size": 8, "media-ffmpeg-pool-size": 8,
"media-image-size-hint": 5242880,
"media-local-max-size": 420, "media-local-max-size": 420,
"media-remote-cache-days": 30, "media-remote-cache-days": 30,
"media-remote-max-size": 420, "media-remote-max-size": 420,
"media-video-size-hint": 41943040,
"metrics-auth-enabled": false, "metrics-auth-enabled": false,
"metrics-auth-password": "", "metrics-auth-password": "",
"metrics-auth-username": "", "metrics-auth-username": "",
@ -244,12 +246,14 @@ GTS_ACCOUNTS_REGISTRATION_OPEN=true \
GTS_ACCOUNTS_REASON_REQUIRED=false \ GTS_ACCOUNTS_REASON_REQUIRED=false \
GTS_MEDIA_DESCRIPTION_MIN_CHARS=69 \ GTS_MEDIA_DESCRIPTION_MIN_CHARS=69 \
GTS_MEDIA_DESCRIPTION_MAX_CHARS=5000 \ GTS_MEDIA_DESCRIPTION_MAX_CHARS=5000 \
GTS_MEDIA_IMAGE_SIZE_HINT='5MiB' \
GTS_MEDIA_LOCAL_MAX_SIZE=420 \ GTS_MEDIA_LOCAL_MAX_SIZE=420 \
GTS_MEDIA_REMOTE_MAX_SIZE=420 \ GTS_MEDIA_REMOTE_MAX_SIZE=420 \
GTS_MEDIA_REMOTE_CACHE_DAYS=30 \ GTS_MEDIA_REMOTE_CACHE_DAYS=30 \
GTS_MEDIA_EMOJI_LOCAL_MAX_SIZE=420 \ GTS_MEDIA_EMOJI_LOCAL_MAX_SIZE=420 \
GTS_MEDIA_EMOJI_REMOTE_MAX_SIZE=420 \ GTS_MEDIA_EMOJI_REMOTE_MAX_SIZE=420 \
GTS_MEDIA_FFMPEG_POOL_SIZE=8 \ GTS_MEDIA_FFMPEG_POOL_SIZE=8 \
GTS_MEDIA_VIDEO_SIZE_HINT='40MiB' \
GTS_METRICS_AUTH_ENABLED=false \ GTS_METRICS_AUTH_ENABLED=false \
GTS_METRICS_ENABLED=false \ GTS_METRICS_ENABLED=false \
GTS_STORAGE_BACKEND='local' \ GTS_STORAGE_BACKEND='local' \

View file

@ -1,5 +0,0 @@
# go-mimetypes
A generated lookup map of file extensions to mimetypes, from data provided at: https://raw.githubusercontent.com/micnic/mime.json/master/index.json
This allows determining mimetype without relying on OS mimetype lookups.

View file

@ -1,42 +0,0 @@
#!/bin/sh
# Mime types JSON source
URL='https://raw.githubusercontent.com/micnic/mime.json/master/index.json'
# Define intro to file
FILE='
// This is an automatically generated file, do not edit
package mimetypes
// MimeTypes is a map of file extensions to mime types.
var MimeTypes = map[string]string{
'
# Set break on new-line
IFS='
'
for line in $(curl -fL "$URL" | grep -E '".+"\s*:\s*".+"'); do
# Trim final whitespace
line=$(echo "$line" | sed -e 's|\s*$||')
# Ensure it ends in a comma
[ "${line%,}" = "$line" ] && line="${line},"
# Add to file
FILE="${FILE}${line}
"
done
# Add final statement to file
FILE="${FILE}
}
"
# Write to file
echo "$FILE" > 'mime.gen.go'
# Check for valid go
gofumpt -w 'mime.gen.go'

File diff suppressed because it is too large Load diff

View file

@ -1,47 +0,0 @@
package mimetypes
import "path"
// PreferredExts defines preferred file
// extensions for input mime types (as there
// can be multiple extensions per mime type).
var PreferredExts = map[string]string{
MimeTypes["mp3"]: "mp3", // audio/mpeg
MimeTypes["mpeg"]: "mpeg", // video/mpeg
}
// GetForFilename returns mimetype for given filename.
func GetForFilename(filename string) (string, bool) {
ext := path.Ext(filename)
if len(ext) < 1 {
return "", false
}
mime, ok := MimeTypes[ext[1:]]
return mime, ok
}
// GetFileExt returns the file extension to use for mimetype. Relying first upon
// the 'PreferredExts' map. It simply returns the first match there may multiple.
func GetFileExt(mimeType string) (string, bool) {
ext, ok := PreferredExts[mimeType]
if ok {
return ext, true
}
for ext, mime := range MimeTypes {
if mime == mimeType {
return ext, true
}
}
return "", false
}
// GetFileExts returns known file extensions used for mimetype.
func GetFileExts(mimeType string) []string {
var exts []string
for ext, mime := range MimeTypes {
if mime == mimeType {
exts = append(exts, ext)
}
}
return exts
}

3
vendor/modules.txt vendored
View file

@ -48,9 +48,6 @@ codeberg.org/gruf/go-maps
# codeberg.org/gruf/go-mempool v0.0.0-20240507125005-cef10d64a760 # codeberg.org/gruf/go-mempool v0.0.0-20240507125005-cef10d64a760
## explicit; go 1.22.2 ## explicit; go 1.22.2
codeberg.org/gruf/go-mempool codeberg.org/gruf/go-mempool
# codeberg.org/gruf/go-mimetypes v1.2.0
## explicit; go 1.17
codeberg.org/gruf/go-mimetypes
# codeberg.org/gruf/go-mutexes v1.5.1 # codeberg.org/gruf/go-mutexes v1.5.1
## explicit; go 1.22.2 ## explicit; go 1.22.2
codeberg.org/gruf/go-mutexes codeberg.org/gruf/go-mutexes