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 | ||||
| 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 | ||||
| generate: | ||||
|  |  | |||
|  | @ -6,13 +6,19 @@ import ( | |||
| 	"crypto/sha256" | ||||
| 	"encoding/base64" | ||||
| 	"encoding/hex" | ||||
| 	"image" | ||||
| 	_ "image/gif" | ||||
| 	"image/jpeg" | ||||
| 	_ "image/png" | ||||
| 	"io" | ||||
| 	"os/exec" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"emperror.dev/errors" | ||||
| 	"github.com/disintegration/imaging" | ||||
| 	"github.com/minio/minio-go/v7" | ||||
| 	"github.com/rs/xid" | ||||
| 
 | ||||
| 	"github.com/chai2010/webp" | ||||
| ) | ||||
| 
 | ||||
| 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. | ||||
| func (db *DB) ConvertAvatar(data string) ( | ||||
| 	webp *bytes.Buffer, | ||||
| 	jpg *bytes.Buffer, | ||||
| 	webpOut *bytes.Buffer, | ||||
| 	jpgOut *bytes.Buffer, | ||||
| 	err error, | ||||
| ) { | ||||
| 	data = strings.TrimSpace(data) | ||||
|  | @ -34,113 +40,36 @@ func (db *DB) ConvertAvatar(data string) ( | |||
| 		return nil, nil, ErrInvalidDataURI | ||||
| 	} | ||||
| 	split := strings.Split(data, ",") | ||||
| 	rest, b64 := split[0], split[1] | ||||
| 
 | ||||
| 	rest = strings.Split(rest, ":")[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) | ||||
| 	rawData, err := base64.StdEncoding.DecodeString(split[1]) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, errors.Wrap(err, "invalid base64 data") | ||||
| 	} | ||||
| 
 | ||||
| 	// create webp convert command and get its pipes | ||||
| 	webpConvert := exec.Command("convert", append(contentArg, webpArgs...)...) | ||||
| 	stdIn, err := webpConvert.StdinPipe() | ||||
| 	img, _, err := image.Decode(bytes.NewReader(rawData)) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, errors.Wrap(err, "getting webp stdin") | ||||
| 	} | ||||
| 	stdOut, err := webpConvert.StdoutPipe() | ||||
| 	if err != nil { | ||||
| 		return nil, nil, errors.Wrap(err, "getting webp stdout") | ||||
| 		return nil, nil, errors.Wrap(err, "decodign image") | ||||
| 	} | ||||
| 
 | ||||
| 	// start webp command | ||||
| 	err = webpConvert.Start() | ||||
| 	resized := imaging.Fill(img, 512, 512, imaging.Center, imaging.Linear) | ||||
| 
 | ||||
| 	webpOut = new(bytes.Buffer) | ||||
| 	err = webp.Encode(webpOut, resized, &webp.Options{ | ||||
| 		Quality: 90, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, errors.Wrap(err, "starting webp command") | ||||
| 		return nil, nil, errors.Wrap(err, "encoding WebP image") | ||||
| 	} | ||||
| 
 | ||||
| 	// write data | ||||
| 	_, err = stdIn.Write(rawData) | ||||
| 	jpgOut = new(bytes.Buffer) | ||||
| 	err = jpeg.Encode(jpgOut, resized, &jpeg.Options{ | ||||
| 		Quality: 80, | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, errors.Wrap(err, "writing webp data") | ||||
| 	} | ||||
| 	err = stdIn.Close() | ||||
| 	if err != nil { | ||||
| 		return nil, nil, errors.Wrap(err, "closing webp stdin") | ||||
| 		return nil, nil, errors.Wrap(err, "encoding JPEG image") | ||||
| 	} | ||||
| 
 | ||||
| 	// read webp output | ||||
| 	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 | ||||
| 	return webpOut, jpgOut, nil | ||||
| } | ||||
| 
 | ||||
| func (db *DB) WriteUserAvatar(ctx context.Context, | ||||
|  |  | |||
							
								
								
									
										3
									
								
								go.mod
									
										
									
									
									
								
							
							
						
						
									
										3
									
								
								go.mod
									
										
									
									
									
								
							|  | @ -6,6 +6,8 @@ require ( | |||
| 	emperror.dev/errors v0.8.1 | ||||
| 	github.com/Masterminds/squirrel v1.5.2 | ||||
| 	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/go-chi/chi/v5 v5.0.7 | ||||
| 	github.com/go-chi/httprate v0.5.3 | ||||
|  | @ -56,6 +58,7 @@ require ( | |||
| 	go.uber.org/atomic v1.7.0 // indirect | ||||
| 	go.uber.org/multierr v1.6.0 // 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/sys v0.0.0-20220722155257-8c9f86f7a55f // 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/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= | ||||
| 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/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= | ||||
| 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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||
| 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/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= | ||||
| 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/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-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-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= | ||||
| golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue