feat: switch to Go libraries for avatar conversion instead of ImageMagick

This commit is contained in:
Sam 2023-03-30 14:44:32 +02:00
parent 65fa7f6d46
commit ab39f64ad5
No known key found for this signature in database
GPG key ID: B4EF20DDE721CAA1
4 changed files with 36 additions and 97 deletions

View file

@ -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:

View file

@ -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
View file

@ -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
View file

@ -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=