mirror of https://github.com/jlelse/GoBlog
Fix media endpoint, add ShortPixel as preferred compression method
This commit is contained in:
parent
d5b37eba73
commit
66ada4c0ae
|
@ -174,6 +174,7 @@ type configMicropubMedia struct {
|
|||
BunnyStorageKey string `mapstructure:"bunnyStorageKey"`
|
||||
BunnyStorageName string `mapstructure:"bunnyStorageName"`
|
||||
TinifyKey string `mapstructure:"tinifyKey"`
|
||||
ShortPixelKey string `mapstructure:"shortPixelKey"`
|
||||
}
|
||||
|
||||
type configRegexRedirect struct {
|
||||
|
|
170
micropubMedia.go
170
micropubMedia.go
|
@ -1,7 +1,9 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
@ -25,10 +27,6 @@ func serveMicropubMedia(w http.ResponseWriter, r *http.Request) {
|
|||
serveError(w, r, "media scope missing", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
if appConfig.Micropub.MediaStorage == nil {
|
||||
serveError(w, r, "Not configured", http.StatusNotImplemented)
|
||||
return
|
||||
}
|
||||
if ct := r.Header.Get(contentType); !strings.Contains(ct, contentTypeMultipartForm) {
|
||||
serveError(w, r, "wrong content-type", http.StatusBadRequest)
|
||||
return
|
||||
|
@ -70,15 +68,30 @@ func serveMicropubMedia(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
// Try to compress file
|
||||
if ms := appConfig.Micropub.MediaStorage; ms != nil && ms.TinifyKey != "" {
|
||||
compressedLocation, err := tinify(location, ms)
|
||||
if err != nil {
|
||||
serveError(w, r, "failed to compress file: "+err.Error(), http.StatusInternalServerError)
|
||||
if ms := appConfig.Micropub.MediaStorage; ms != nil {
|
||||
serveCompressionError := func(ce error) {
|
||||
serveError(w, r, "failed to compress file: "+ce.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
var compressedLocation string
|
||||
var compressionErr error
|
||||
// Default ShortPixel
|
||||
if ms.ShortPixelKey != "" {
|
||||
compressedLocation, compressionErr = shortPixel(location, ms)
|
||||
}
|
||||
if compressionErr != nil {
|
||||
serveCompressionError(compressionErr)
|
||||
return
|
||||
} else if compressedLocation != "" {
|
||||
}
|
||||
// Fallback Tinify
|
||||
if compressedLocation == "" && ms.TinifyKey != "" {
|
||||
compressedLocation, compressionErr = tinify(location, ms)
|
||||
}
|
||||
if compressionErr != nil {
|
||||
serveCompressionError(compressionErr)
|
||||
return
|
||||
}
|
||||
if compressedLocation != "" {
|
||||
location = compressedLocation
|
||||
} else {
|
||||
serveError(w, r, "No compressed location", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
http.Redirect(w, r, location, http.StatusCreated)
|
||||
|
@ -96,10 +109,13 @@ func uploadFile(filename string, f io.Reader) (string, error) {
|
|||
if ms != nil && ms.MediaURL != "" {
|
||||
return ms.MediaURL + loc, nil
|
||||
}
|
||||
return loc, nil
|
||||
return appConfig.Server.PublicAddress + loc, nil
|
||||
}
|
||||
|
||||
func uploadToBunny(filename string, f io.Reader, config *configMicropubMedia) (location string, err error) {
|
||||
if config == nil || config.BunnyStorageName == "" || config.BunnyStorageKey == "" || config.MediaURL == "" {
|
||||
return "", errors.New("Bunny storage not completely configured")
|
||||
}
|
||||
req, _ := http.NewRequest(http.MethodPut, fmt.Sprintf("https://storage.bunnycdn.com/%s/%s", url.PathEscape(config.BunnyStorageName), url.PathEscape(filename)), f)
|
||||
req.Header.Add("AccessKey", config.BunnyStorageKey)
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
|
@ -110,53 +126,118 @@ func uploadToBunny(filename string, f io.Reader, config *configMicropubMedia) (l
|
|||
}
|
||||
|
||||
func tinify(url string, config *configMicropubMedia) (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)) {
|
||||
// Check config
|
||||
if config == nil || config.TinifyKey == "" {
|
||||
return "", errors.New("Tinify not configured")
|
||||
}
|
||||
// Check url
|
||||
fileExtension, allowed := compressionIsSupported(url, "jpg", "jpeg", "png")
|
||||
if !allowed {
|
||||
return "", nil
|
||||
}
|
||||
// Compress
|
||||
tfgo.SetKey(config.TinifyKey)
|
||||
s, err := tfgo.FromUrl(url)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = s.Resize(&tfgo.ResizeOption{
|
||||
if err = s.Resize(&tfgo.ResizeOption{
|
||||
Method: tfgo.ResizeMethodScale,
|
||||
Width: 2000,
|
||||
})
|
||||
if err != nil {
|
||||
}); err != nil {
|
||||
return "", err
|
||||
}
|
||||
file, err := ioutil.TempFile("", "tiny-*."+fileExtension)
|
||||
tmpFile, err := ioutil.TempFile("", "tiny-*."+fileExtension)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer func() {
|
||||
_ = file.Close()
|
||||
_ = os.Remove(file.Name())
|
||||
_ = tmpFile.Close()
|
||||
_ = os.Remove(tmpFile.Name())
|
||||
}()
|
||||
err = s.ToFile(file.Name())
|
||||
if err = s.ToFile(tmpFile.Name()); err != nil {
|
||||
return "", err
|
||||
}
|
||||
fileName, err := hashFile(tmpFile.Name())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
hashFile, err := os.Open(file.Name())
|
||||
defer func() { _ = hashFile.Close() }()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
fileName, err := getSHA256(hashFile)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
location, err = uploadFile(fileName+"."+fileExtension, file)
|
||||
// Upload compressed file
|
||||
location, err = uploadFile(fileName+"."+fileExtension, tmpFile)
|
||||
return
|
||||
}
|
||||
|
||||
func shortPixel(url string, config *configMicropubMedia) (location string, err error) {
|
||||
// Check config
|
||||
if config == nil || config.ShortPixelKey == "" {
|
||||
return "", errors.New("ShortPixel not configured")
|
||||
}
|
||||
// Check url
|
||||
fileExtension, allowed := compressionIsSupported(url, "jpg", "jpeg", "png")
|
||||
if !allowed {
|
||||
return "", nil
|
||||
}
|
||||
// Compress
|
||||
var buf bytes.Buffer
|
||||
_ = json.NewEncoder(&buf).Encode(map[string]interface{}{
|
||||
"key": config.ShortPixelKey,
|
||||
"plugin_version": "GB001",
|
||||
"lossy": 1,
|
||||
"resize": 3,
|
||||
"resize_width": 2000,
|
||||
"resize_height": 3000,
|
||||
"cmyk2rgb": 1,
|
||||
"keep_exif": 0,
|
||||
"url": url,
|
||||
})
|
||||
req, err := http.NewRequest(http.MethodPut, "https://api.shortpixel.com/v2/reducer-sync.php", &buf)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
} else if resp.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("failed to compress image, status code %d", resp.StatusCode)
|
||||
}
|
||||
tmpFile, err := ioutil.TempFile("", "tiny-*."+fileExtension)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
tmpFileName := tmpFile.Name()
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
_ = tmpFile.Close()
|
||||
_ = os.Remove(tmpFileName)
|
||||
}()
|
||||
if _, err = io.Copy(tmpFile, resp.Body); err != nil {
|
||||
return "", err
|
||||
}
|
||||
fileName, err := hashFile(tmpFileName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Reopen tmp file
|
||||
_ = tmpFile.Close()
|
||||
tmpFile, err = os.Open(tmpFileName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Upload compressed file
|
||||
location, err = uploadFile(fileName+"."+fileExtension, tmpFile)
|
||||
return
|
||||
}
|
||||
|
||||
func compressionIsSupported(url string, allowed ...string) (string, bool) {
|
||||
spliced := strings.Split(url, ".")
|
||||
ext := spliced[len(spliced)-1]
|
||||
sort.Strings(allowed)
|
||||
if i := sort.SearchStrings(allowed, strings.ToLower(ext)); i >= len(allowed) || allowed[i] != strings.ToLower(ext) {
|
||||
return ext, false
|
||||
}
|
||||
return ext, true
|
||||
}
|
||||
|
||||
func getSHA256(file multipart.File) (filename string, err error) {
|
||||
h := sha256.New()
|
||||
if _, err = io.Copy(h, file); err != nil {
|
||||
|
@ -164,3 +245,18 @@ func getSHA256(file multipart.File) (filename string, err error) {
|
|||
}
|
||||
return fmt.Sprintf("%x", h.Sum(nil)), nil
|
||||
}
|
||||
|
||||
func hashFile(filename string) (string, error) {
|
||||
hashFile, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer func() {
|
||||
_ = hashFile.Close()
|
||||
}()
|
||||
fn, err := getSHA256(hashFile)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fn, nil
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue