diff --git a/docs/v3-api.yaml b/docs/v3-api.yaml index 890891e44..3cd768e90 100644 --- a/docs/v3-api.yaml +++ b/docs/v3-api.yaml @@ -802,7 +802,7 @@ paths: $ref: "#/components/schemas/PostStampRequest" encoding: file: - contentType: "image/png, image/jpeg, image/gif, image/svg+xml" + contentType: "image/png, image/jpeg, image/gif, image/webp, image/svg+xml" description: "" operationId: createStamp tags: @@ -1094,7 +1094,7 @@ paths: $ref: "#/components/schemas/PutUserIconRequest" encoding: file: - contentType: "image/png, image/jpeg, image/gif" + contentType: "image/png, image/jpeg, image/gif, image/webp" description: "" tags: - group @@ -1714,6 +1714,10 @@ paths: schema: type: string format: binary + image/webp: + schema: + type: string + format: binary image/png: schema: type: string @@ -1747,7 +1751,7 @@ paths: $ref: "#/components/schemas/PutUserIconRequest" encoding: file: - contentType: "image/png, image/jpeg, image/gif" + contentType: "image/png, image/jpeg, image/gif, image/webp" description: "" tags: - webhook @@ -1771,6 +1775,10 @@ paths: schema: type: string format: binary + image/webp: + schema: + type: string + format: binary image/png: schema: type: string @@ -1806,7 +1814,7 @@ paths: $ref: "#/components/schemas/PutUserIconRequest" encoding: file: - contentType: "image/png, image/jpeg, image/gif" + contentType: "image/png, image/jpeg, image/gif, image/webp" tags: - user description: |- @@ -1829,6 +1837,10 @@ paths: schema: type: string format: binary + image/webp: + schema: + type: string + format: binary image/png: schema: type: string @@ -1859,7 +1871,7 @@ paths: $ref: "#/components/schemas/PutUserIconRequest" encoding: file: - contentType: "image/png, image/jpeg, image/gif" + contentType: "image/png, image/jpeg, image/gif, image/webp" tags: - me /users/me/password: @@ -2935,6 +2947,10 @@ paths: schema: type: string format: binary + image/webp: + schema: + type: string + format: binary image/png: schema: type: string @@ -3201,6 +3217,10 @@ paths: schema: type: string format: binary + image/webp: + schema: + type: string + format: binary image/png: schema: type: string @@ -3238,7 +3258,7 @@ paths: $ref: "#/components/schemas/PutUserIconRequest" encoding: file: - contentType: "image/png, image/jpeg, image/gif" + contentType: "image/png, image/jpeg, image/gif, image/webp" tags: - bot description: |- @@ -3982,6 +4002,10 @@ paths: schema: type: string format: binary + image/webp: + schema: + type: string + format: binary image/jpeg: schema: type: string @@ -4018,7 +4042,7 @@ paths: file: type: string format: binary - description: "スタンプ画像(1MBまでのpng, jpeg, gif)" + description: "スタンプ画像(1MBまでのpng, jpeg, gif, 静止webp)" required: - file description: "" @@ -5238,7 +5262,7 @@ components: file: type: string format: binary - description: "スタンプ画像(1MBまでのpng, jpeg, gif)" + description: "スタンプ画像(1MBまでのpng, jpeg, gif, 静止webp)" required: - name - file @@ -5987,7 +6011,7 @@ components: file: type: string format: binary - description: "アイコン画像(2MB,`Config.Imaging.MaxPixels`(default: 2560*1600)までのpng, jpeg, gif)" + description: "アイコン画像(2MB,`Config.Imaging.MaxPixels`(default: 2560*1600)までのpng, jpeg, gif, 静止webp)" required: - file PutMyPasswordRequest: diff --git a/go.mod b/go.mod index 4998c1e21..bf3a24851 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ toolchain go1.27.1 require ( cloud.google.com/go/profiler v0.6.0 firebase.google.com/go/v4 v4.20.0 + github.com/HugoSmits86/nativewebp v1.3.0 github.com/MicahParks/jwkset v0.11.3 github.com/NYTimes/gziphandler v1.1.1 github.com/aws/aws-sdk-go-v2 v1.45.1 diff --git a/go.sum b/go.sum index 9e48cac5b..107e0e121 100644 --- a/go.sum +++ b/go.sum @@ -86,6 +86,8 @@ github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0 github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.56.0/go.mod h1:rqP9UEhOXv9WhQ7Gjz+G5y/pf8+BJZW5/Ts0AhE0PwE= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.56.0 h1:0YP0+/ixwu+Uqeu/FGiBZNQ19huiUxxiPXIc9WsLKuQ= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.56.0/go.mod h1:6ZZMQhZKDvUvkJw2rc+oDP90tMMzuU/J+5HG1ZmPOmE= +github.com/HugoSmits86/nativewebp v1.3.0 h1:n1egtEzSV4KwFtealr7dzdYq1wI/uj/bOQ/QcTcIyVE= +github.com/HugoSmits86/nativewebp v1.3.0/go.mod h1:YNQuWenlVmSUUASVNhTDwf4d7FwYQGbGhklC8p72Vr8= github.com/MicahParks/jwkset v0.11.3 h1:Phli4RdTDdIdLXZpuO7abkwZyzIk0RDTUPVVBHPRdkQ= github.com/MicahParks/jwkset v0.11.3/go.mod h1:U2oRhRaLgDCLjtpGL2GseNKGmZtLs/3O7p+OZaL5vo0= github.com/MicahParks/keyfunc v1.9.0 h1:lhKd5xrFHLNOWrDc4Tyb/Q1AJ4LCzQ48GVJyVIID3+o= diff --git a/router/consts/mime_types.go b/router/consts/mime_types.go index 25a9fb24b..c1066564e 100644 --- a/router/consts/mime_types.go +++ b/router/consts/mime_types.go @@ -5,4 +5,5 @@ const ( MimeImageJPEG = "image/jpeg" MimeImageGIF = "image/gif" MimeImageSVG = "image/svg+xml" + MimeImageWebP = "image/webp" ) diff --git a/router/utils/process_image.go b/router/utils/process_image.go index 08af91a3b..644e06cbf 100644 --- a/router/utils/process_image.go +++ b/router/utils/process_image.go @@ -3,11 +3,14 @@ package utils import ( "bytes" + "errors" "image/png" "io" + "github.com/HugoSmits86/nativewebp" "github.com/gofrs/uuid" "github.com/labstack/echo/v5" + "github.com/sapphi-red/midec" "github.com/traPtitech/traQ/model" "github.com/traPtitech/traQ/router/consts" @@ -60,10 +63,10 @@ func saveUploadImage(p imaging.Processor, c *echo.Context, m file.Manager, name case consts.MimeImagePNG, consts.MimeImageJPEG: img, err := p.Fit(src, maxImageSize, maxImageSize) if err != nil { - switch err { - case imaging.ErrInvalidImageSrc: + switch { + case errors.Is(err, imaging.ErrInvalidImageSrc): return uuid.Nil, herror.BadRequest(badImage) - case imaging.ErrPixelLimitExceeded: + case err == imaging.ErrPixelLimitExceeded: return uuid.Nil, herror.BadRequest(tooLargeImage) default: return uuid.Nil, herror.InternalServerError(err) @@ -71,7 +74,7 @@ func saveUploadImage(p imaging.Processor, c *echo.Context, m file.Manager, name } // PNGに変換 - b := bytes.Buffer{} + var b bytes.Buffer if err := png.Encode(&b, img); err != nil { return uuid.Nil, herror.InternalServerError(err) } @@ -81,6 +84,37 @@ func saveUploadImage(p imaging.Processor, c *echo.Context, m file.Manager, name args.MimeType = consts.MimeImagePNG args.Thumbnail = img // サムネイル画像より小さいという前提 + case consts.MimeImageWebP: + isAnimated, err := midec.IsAnimated(src) + if _, err := src.Seek(0, io.SeekStart); err != nil { + return uuid.Nil, herror.InternalServerError(err) + } + if err != nil { + return uuid.Nil, herror.BadRequest(badImage) + } + // TODO: アニメーションWebP対応 + if isAnimated { + return uuid.Nil, herror.BadRequest("animated WebP is not supported") + } + img, err := p.Fit(src, maxImageSize, maxImageSize) + if err != nil { + switch { + case errors.Is(err, imaging.ErrInvalidImageSrc): + return uuid.Nil, herror.BadRequest(badImage) + case err == imaging.ErrPixelLimitExceeded: + return uuid.Nil, herror.BadRequest(tooLargeImage) + default: + return uuid.Nil, herror.InternalServerError(err) + } + } + var b bytes.Buffer + if err := nativewebp.Encode(&b, img, nil); err != nil { + return uuid.Nil, herror.InternalServerError(err) + } + args.Src = bytes.NewReader(b.Bytes()) + args.FileSize = int64(b.Len()) + args.MimeType = consts.MimeImageWebP + args.Thumbnail = img case consts.MimeImageGIF: // リサイズ b, err := p.FitAnimationGIF(src, maxImageSize, maxImageSize)