From 9ca7da9ae5f6711c09476e2bb07a346c7345be86 Mon Sep 17 00:00:00 2001 From: Jan-Lukas Else Date: Sun, 19 Jan 2020 10:55:18 +0100 Subject: [PATCH] Add support for image compression --- config.go | 17 +++++++++++- go.mod | 1 + go.sum | 2 ++ imagecompression.go | 65 +++++++++++++++++++++++++++++++++++++++++++++ mediaendpoint.go | 18 +++++++------ mediastorage.go | 12 +++++++++ 6 files changed, 106 insertions(+), 9 deletions(-) create mode 100644 imagecompression.go diff --git a/config.go b/config.go index 8bf3724..8b5b284 100644 --- a/config.go +++ b/config.go @@ -9,6 +9,7 @@ import ( var ( BlogUrl string + MediaEndpointUrl string IgnoredWebmentionUrls []string SyndicationTargets []SyndicationTarget SelectedStorage Storage @@ -16,7 +17,7 @@ var ( SelectedCdn Cdn SelectedSocials Socials SelectedNotificationServices NotificationServices - MediaEndpointUrl string + SelectedImageCompression ImageCompression ) type SyndicationTarget struct { @@ -39,6 +40,7 @@ type config struct { TelegramBotToken string `env:"TELEGRAM_BOT_TOKEN"` IgnoredWebmentionUrls []string `env:"WEBMENTION_IGNORED" envSeparator:","` SyndicationTargets []string `env:"SYNDICATION" envSeparator:","` + TinifyKey string `env:"TINIFY_KEY"` } func initConfig() (err error) { @@ -138,5 +140,18 @@ func initConfig() (err error) { if SelectedNotificationServices == nil { log.Println("No notification services configured") } + // Find configured image compression service (optional) + SelectedImageCompression = func() ImageCompression { + // Tinify + if len(cfg.TinifyKey) > 0 { + return &Tinify{ + key: cfg.TinifyKey, + } + } + return nil + }() + if SelectedImageCompression == nil { + log.Println("no image compression configured") + } return nil } diff --git a/go.mod b/go.mod index 3d9f1f8..81c2009 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.13 require ( github.com/caarlos0/env/v6 v6.1.0 github.com/go-telegram-bot-api/telegram-bot-api v4.6.5-0.20190904012038-b33efeebc785+incompatible + github.com/gwpp/tinify-go v0.0.0-20170613055357-77b9df15f343 github.com/technoweenie/multipartstreamer v1.0.1 // indirect gopkg.in/yaml.v2 v2.2.7 willnorris.com/go/webmention v0.0.0-20191104072158-c7fb13569b62 diff --git a/go.sum b/go.sum index 3ad67e0..34012cf 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,8 @@ github.com/go-telegram-bot-api/telegram-bot-api v4.6.5-0.20190904012038-b33efeeb github.com/go-telegram-bot-api/telegram-bot-api v4.6.5-0.20190904012038-b33efeebc785+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM= github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/gwpp/tinify-go v0.0.0-20170613055357-77b9df15f343 h1:QbWv77mi5YeTWR3FxLqx5eeC2H0fUL07Kj2Uhza9/A8= +github.com/gwpp/tinify-go v0.0.0-20170613055357-77b9df15f343/go.mod h1:FP1q8rlReJYgAj3zqyQfD745uokKzl0baffXAcxDcl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/imagecompression.go b/imagecompression.go new file mode 100644 index 0000000..23e8fd3 --- /dev/null +++ b/imagecompression.go @@ -0,0 +1,65 @@ +package main + +import ( + "errors" + tfgo "github.com/gwpp/tinify-go/tinify" + "io/ioutil" + "os" + "sort" + "strings" +) + +type ImageCompression interface { + Compress(url string) (location string, err error) +} + +// Tinify +type Tinify struct { + // API Key + key string +} + +func (t Tinify) Compress(url string) (location string, err error) { + fileExtension := func() string { + spliced := strings.Split(url, ".") + return spliced[len(spliced)-1] + }() + supportedTypes := []string{"jpg", "jpeg", "png"} + sort.Strings(supportedTypes) + i := sort.SearchStrings(supportedTypes, strings.ToLower(fileExtension)) + if !(i < len(supportedTypes) && supportedTypes[i] == strings.ToLower(fileExtension)) { + err = errors.New("file not supported") + return + } + tfgo.SetKey(t.key) + s, e := tfgo.FromUrl(url) + if e != nil { + err = errors.New("failed to compress file") + return + } + file, e := ioutil.TempFile("", "tiny-*."+fileExtension) + if e != nil { + err = errors.New("failed to create temporary file") + return + } + defer func() { + _ = os.Remove(file.Name()) + }() + e = s.ToFile(file.Name()) + if e != nil { + err = errors.New("failed to save compressed file") + return + } + hashFile, e := os.Open(file.Name()) + defer func() { _ = hashFile.Close() }() + if e != nil { + err = errors.New("failed to open temporary file") + return + } + fileName, err := getSHA256(hashFile) + if err != nil { + return + } + location, err = SelectedMediaStorage.Upload(fileName+"."+fileExtension, file) + return +} diff --git a/mediaendpoint.go b/mediaendpoint.go index b22146f..0db5708 100644 --- a/mediaendpoint.go +++ b/mediaendpoint.go @@ -1,9 +1,6 @@ package main import ( - "crypto/sha256" - "fmt" - "io" "mime" "net/http" "path/filepath" @@ -44,14 +41,13 @@ func HandleMedia(w http.ResponseWriter, r *http.Request) { return } hashFile, _, _ := r.FormFile("file") - h := sha256.New() defer func() { _ = hashFile.Close() }() - if _, err := io.Copy(h, hashFile); err != nil { + fileName, err := getSHA256(hashFile) + if err != nil { w.WriteHeader(http.StatusInternalServerError) - _, _ = w.Write([]byte("Failed to calculate hash of file")) + _, _ = w.Write([]byte(err.Error())) return } - fileName := fmt.Sprintf("%x", h.Sum(nil)) fileExtension := filepath.Ext(header.Filename) if len(fileExtension) == 0 { // Find correct file extension if original filename does not contain one @@ -67,9 +63,15 @@ func HandleMedia(w http.ResponseWriter, r *http.Request) { location, err := SelectedMediaStorage.Upload(fileName, file) if err != nil { w.WriteHeader(http.StatusInternalServerError) - _, _ = w.Write([]byte("Failed to upload file")) + _, _ = w.Write([]byte("Failed to upload original file")) return } + if SelectedImageCompression != nil { + compressedLocation, err := SelectedImageCompression.Compress(location) + if err == nil && len(compressedLocation) > 0 { + location = compressedLocation + } + } w.Header().Add("Location", location) w.WriteHeader(http.StatusCreated) } else { diff --git a/mediastorage.go b/mediastorage.go index b94b528..1886b49 100644 --- a/mediastorage.go +++ b/mediastorage.go @@ -1,7 +1,10 @@ package main import ( + "crypto/sha256" "errors" + "fmt" + "io" "mime/multipart" "net/http" "net/url" @@ -33,3 +36,12 @@ func (b BunnyCdnStorage) Upload(fileName string, file multipart.File) (location } return b.baseLocation + fileName, nil } + +func getSHA256(file multipart.File) (filename string, err error) { + h := sha256.New() + if _, e := io.Copy(h, file); e != nil { + err = errors.New("failed to calculate hash of file") + return + } + return fmt.Sprintf("%x", h.Sum(nil)), nil +}