feat: switch to Go libraries for avatar conversion instead of ImageMagick
This commit is contained in:
parent
65fa7f6d46
commit
ab39f64ad5
4 changed files with 36 additions and 97 deletions
2
Makefile
2
Makefile
|
@ -2,7 +2,7 @@ all: generate backend frontend
|
||||||
|
|
||||||
.PHONY: backend
|
.PHONY: backend
|
||||||
backend:
|
backend:
|
||||||
CGO_ENABLED=0 go build -v -o pronouns -ldflags="-buildid= -X codeberg.org/u1f320/pronouns.cc/backend/server.Revision=`git rev-parse --short HEAD` -X codeberg.org/u1f320/pronouns.cc/backend/server.Tag=`git describe --tags --long`" .
|
go build -v -o pronouns -ldflags="-buildid= -X codeberg.org/u1f320/pronouns.cc/backend/server.Revision=`git rev-parse --short HEAD` -X codeberg.org/u1f320/pronouns.cc/backend/server.Tag=`git describe --tags --long`" .
|
||||||
|
|
||||||
.PHONY: generate
|
.PHONY: generate
|
||||||
generate:
|
generate:
|
||||||
|
|
|
@ -6,13 +6,19 @@ import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"image"
|
||||||
|
_ "image/gif"
|
||||||
|
"image/jpeg"
|
||||||
|
_ "image/png"
|
||||||
"io"
|
"io"
|
||||||
"os/exec"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"emperror.dev/errors"
|
"emperror.dev/errors"
|
||||||
|
"github.com/disintegration/imaging"
|
||||||
"github.com/minio/minio-go/v7"
|
"github.com/minio/minio-go/v7"
|
||||||
"github.com/rs/xid"
|
"github.com/rs/xid"
|
||||||
|
|
||||||
|
"github.com/chai2010/webp"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -25,8 +31,8 @@ const ErrInvalidContentType = errors.Sentinel("invalid avatar content type")
|
||||||
|
|
||||||
// ConvertAvatar parses an avatar from a data URI, converts it to WebP and JPEG, and returns the results.
|
// ConvertAvatar parses an avatar from a data URI, converts it to WebP and JPEG, and returns the results.
|
||||||
func (db *DB) ConvertAvatar(data string) (
|
func (db *DB) ConvertAvatar(data string) (
|
||||||
webp *bytes.Buffer,
|
webpOut *bytes.Buffer,
|
||||||
jpg *bytes.Buffer,
|
jpgOut *bytes.Buffer,
|
||||||
err error,
|
err error,
|
||||||
) {
|
) {
|
||||||
data = strings.TrimSpace(data)
|
data = strings.TrimSpace(data)
|
||||||
|
@ -34,113 +40,36 @@ func (db *DB) ConvertAvatar(data string) (
|
||||||
return nil, nil, ErrInvalidDataURI
|
return nil, nil, ErrInvalidDataURI
|
||||||
}
|
}
|
||||||
split := strings.Split(data, ",")
|
split := strings.Split(data, ",")
|
||||||
rest, b64 := split[0], split[1]
|
|
||||||
|
|
||||||
rest = strings.Split(rest, ":")[1]
|
rawData, err := base64.StdEncoding.DecodeString(split[1])
|
||||||
contentType := strings.Split(rest, ";")[0]
|
|
||||||
|
|
||||||
var contentArg []string
|
|
||||||
switch contentType {
|
|
||||||
case "image/png":
|
|
||||||
contentArg = []string{"png:-"}
|
|
||||||
case "image/jpeg":
|
|
||||||
contentArg = []string{"jpg:-"}
|
|
||||||
case "image/gif":
|
|
||||||
contentArg = []string{"gif:-"}
|
|
||||||
case "image/webp":
|
|
||||||
contentArg = []string{"webp:-"}
|
|
||||||
default:
|
|
||||||
return nil, nil, ErrInvalidContentType
|
|
||||||
}
|
|
||||||
|
|
||||||
rawData, err := base64.StdEncoding.DecodeString(b64)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, errors.Wrap(err, "invalid base64 data")
|
return nil, nil, errors.Wrap(err, "invalid base64 data")
|
||||||
}
|
}
|
||||||
|
|
||||||
// create webp convert command and get its pipes
|
img, _, err := image.Decode(bytes.NewReader(rawData))
|
||||||
webpConvert := exec.Command("convert", append(contentArg, webpArgs...)...)
|
|
||||||
stdIn, err := webpConvert.StdinPipe()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, errors.Wrap(err, "getting webp stdin")
|
return nil, nil, errors.Wrap(err, "decodign image")
|
||||||
}
|
|
||||||
stdOut, err := webpConvert.StdoutPipe()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, errors.Wrap(err, "getting webp stdout")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// start webp command
|
resized := imaging.Fill(img, 512, 512, imaging.Center, imaging.Linear)
|
||||||
err = webpConvert.Start()
|
|
||||||
|
webpOut = new(bytes.Buffer)
|
||||||
|
err = webp.Encode(webpOut, resized, &webp.Options{
|
||||||
|
Quality: 90,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, errors.Wrap(err, "starting webp command")
|
return nil, nil, errors.Wrap(err, "encoding WebP image")
|
||||||
}
|
}
|
||||||
|
|
||||||
// write data
|
jpgOut = new(bytes.Buffer)
|
||||||
_, err = stdIn.Write(rawData)
|
err = jpeg.Encode(jpgOut, resized, &jpeg.Options{
|
||||||
|
Quality: 80,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, errors.Wrap(err, "writing webp data")
|
return nil, nil, errors.Wrap(err, "encoding JPEG image")
|
||||||
}
|
|
||||||
err = stdIn.Close()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, errors.Wrap(err, "closing webp stdin")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// read webp output
|
return webpOut, jpgOut, nil
|
||||||
webpBuffer := new(bytes.Buffer)
|
|
||||||
_, err = io.Copy(webpBuffer, stdOut)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, errors.Wrap(err, "reading webp data")
|
|
||||||
}
|
|
||||||
webp = webpBuffer
|
|
||||||
|
|
||||||
// finish webp command
|
|
||||||
err = webpConvert.Wait()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, errors.Wrap(err, "running webp command")
|
|
||||||
}
|
|
||||||
|
|
||||||
// create jpg convert command and get its pipes
|
|
||||||
jpgConvert := exec.Command("convert", append(contentArg, jpgArgs...)...)
|
|
||||||
stdIn, err = jpgConvert.StdinPipe()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, errors.Wrap(err, "getting jpg stdin")
|
|
||||||
}
|
|
||||||
stdOut, err = jpgConvert.StdoutPipe()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, errors.Wrap(err, "getting jpg stdout")
|
|
||||||
}
|
|
||||||
|
|
||||||
// start jpg command
|
|
||||||
err = jpgConvert.Start()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, errors.Wrap(err, "starting jpg command")
|
|
||||||
}
|
|
||||||
|
|
||||||
// write data
|
|
||||||
_, err = stdIn.Write(rawData)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, errors.Wrap(err, "writing jpg data")
|
|
||||||
}
|
|
||||||
err = stdIn.Close()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, errors.Wrap(err, "closing jpg stdin")
|
|
||||||
}
|
|
||||||
|
|
||||||
// read jpg output
|
|
||||||
jpgBuffer := new(bytes.Buffer)
|
|
||||||
_, err = io.Copy(jpgBuffer, stdOut)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, errors.Wrap(err, "reading jpg data")
|
|
||||||
}
|
|
||||||
jpg = jpgBuffer
|
|
||||||
|
|
||||||
// finish jpg command
|
|
||||||
err = jpgConvert.Wait()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, errors.Wrap(err, "running jpg command")
|
|
||||||
}
|
|
||||||
|
|
||||||
return webp, jpg, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *DB) WriteUserAvatar(ctx context.Context,
|
func (db *DB) WriteUserAvatar(ctx context.Context,
|
||||||
|
|
3
go.mod
3
go.mod
|
@ -6,6 +6,8 @@ require (
|
||||||
emperror.dev/errors v0.8.1
|
emperror.dev/errors v0.8.1
|
||||||
github.com/Masterminds/squirrel v1.5.2
|
github.com/Masterminds/squirrel v1.5.2
|
||||||
github.com/bwmarrin/discordgo v0.25.0
|
github.com/bwmarrin/discordgo v0.25.0
|
||||||
|
github.com/chai2010/webp v1.1.1
|
||||||
|
github.com/disintegration/imaging v1.6.2
|
||||||
github.com/georgysavva/scany v0.3.0
|
github.com/georgysavva/scany v0.3.0
|
||||||
github.com/go-chi/chi/v5 v5.0.7
|
github.com/go-chi/chi/v5 v5.0.7
|
||||||
github.com/go-chi/httprate v0.5.3
|
github.com/go-chi/httprate v0.5.3
|
||||||
|
@ -56,6 +58,7 @@ require (
|
||||||
go.uber.org/atomic v1.7.0 // indirect
|
go.uber.org/atomic v1.7.0 // indirect
|
||||||
go.uber.org/multierr v1.6.0 // indirect
|
go.uber.org/multierr v1.6.0 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
|
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
|
||||||
|
golang.org/x/image v0.0.0-20211028202545-6944b10bf410 // indirect
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
|
||||||
golang.org/x/text v0.3.7 // indirect
|
golang.org/x/text v0.3.7 // indirect
|
||||||
|
|
7
go.sum
7
go.sum
|
@ -62,6 +62,8 @@ github.com/bwmarrin/discordgo v0.25.0/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
||||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/chai2010/webp v1.1.1 h1:jTRmEccAJ4MGrhFOrPMpNGIJ/eybIgwKpcACsrTEapk=
|
||||||
|
github.com/chai2010/webp v1.1.1/go.mod h1:0XVwvZWdjjdxpUEIf7b9g9VkHFnInUSYujwqTLEuldU=
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
|
@ -85,6 +87,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||||
|
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||||
|
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
@ -520,6 +524,9 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH
|
||||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
|
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
|
golang.org/x/image v0.0.0-20211028202545-6944b10bf410 h1:hTftEOvwiOq2+O8k2D5/Q7COC7k5Qcrgc2TFURJYnvQ=
|
||||||
|
golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
|
Loading…
Reference in a new issue