2021-04-21 17:47:45 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
2021-06-18 12:32:03 +00:00
|
|
|
|
2021-06-28 20:17:18 +00:00
|
|
|
"go.goblog.app/app/pkgs/contenttype"
|
2021-04-21 17:47:45 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const defaultCompressionWidth = 2000
|
|
|
|
const defaultCompressionHeight = 3000
|
|
|
|
|
2021-06-20 13:18:02 +00:00
|
|
|
type mediaCompression interface {
|
2021-06-23 12:28:51 +00:00
|
|
|
compress(url string, save mediaStorageSaveFunc, hc httpClient) (location string, err error)
|
2021-06-20 13:18:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (a *goBlog) compressMediaFile(url string) (location string, err error) {
|
|
|
|
// Init compressors
|
|
|
|
a.compressorsInit.Do(a.initMediaCompressors)
|
|
|
|
// Try all compressors until success
|
|
|
|
for _, c := range a.compressors {
|
2021-06-23 12:28:51 +00:00
|
|
|
location, err = c.compress(url, a.saveMediaFile, a.httpClient)
|
2021-06-20 13:18:02 +00:00
|
|
|
if location != "" && err == nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Return result
|
|
|
|
return location, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *goBlog) initMediaCompressors() {
|
|
|
|
config := a.cfg.Micropub.MediaStorage
|
|
|
|
if config == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if key := config.ShortPixelKey; key != "" {
|
|
|
|
a.compressors = append(a.compressors, &shortpixel{key})
|
|
|
|
}
|
|
|
|
if key := config.TinifyKey; key != "" {
|
|
|
|
a.compressors = append(a.compressors, &tinify{key})
|
|
|
|
}
|
|
|
|
if config.CloudflareCompressionEnabled {
|
|
|
|
a.compressors = append(a.compressors, &cloudflare{})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-23 12:28:51 +00:00
|
|
|
type shortpixel struct {
|
|
|
|
key string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (sp *shortpixel) compress(url string, upload mediaStorageSaveFunc, hc httpClient) (location string, err error) {
|
2021-06-20 13:18:02 +00:00
|
|
|
// Check url
|
|
|
|
fileExtension, allowed := urlHasExt(url, "jpg", "jpeg", "png")
|
|
|
|
if !allowed {
|
|
|
|
return "", nil
|
|
|
|
}
|
|
|
|
// Compress
|
|
|
|
j, _ := json.Marshal(map[string]interface{}{
|
|
|
|
"key": sp.key,
|
|
|
|
"plugin_version": "GB001",
|
|
|
|
"lossy": 1,
|
|
|
|
"resize": 3,
|
|
|
|
"resize_width": defaultCompressionWidth,
|
|
|
|
"resize_height": defaultCompressionHeight,
|
|
|
|
"cmyk2rgb": 1,
|
|
|
|
"keep_exif": 0,
|
|
|
|
"url": url,
|
|
|
|
})
|
|
|
|
req, err := http.NewRequest(http.MethodPut, "https://api.shortpixel.com/v2/reducer-sync.php", bytes.NewReader(j))
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
resp, err := hc.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
|
|
_, _ = io.Copy(io.Discard, resp.Body)
|
|
|
|
return "", fmt.Errorf("shortpixel failed to compress image, status code %d", resp.StatusCode)
|
|
|
|
}
|
|
|
|
tmpFile, err := os.CreateTemp("", "tiny-*."+fileExtension)
|
|
|
|
if err != nil {
|
|
|
|
_, _ = io.Copy(io.Discard, resp.Body)
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
_ = tmpFile.Close()
|
|
|
|
_ = os.Remove(tmpFile.Name())
|
|
|
|
}()
|
|
|
|
if _, err = io.Copy(tmpFile, resp.Body); err != nil {
|
|
|
|
_, _ = io.Copy(io.Discard, resp.Body)
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
fileName, err := getSHA256(tmpFile)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
2021-04-21 17:47:45 +00:00
|
|
|
}
|
2021-06-20 13:18:02 +00:00
|
|
|
// Upload compressed file
|
|
|
|
location, err = upload(fileName+"."+fileExtension, tmpFile)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-06-23 12:28:51 +00:00
|
|
|
type tinify struct {
|
|
|
|
key string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (tf *tinify) compress(url string, upload mediaStorageSaveFunc, hc httpClient) (location string, err error) {
|
2021-04-21 17:47:45 +00:00
|
|
|
// Check url
|
2021-06-20 13:18:02 +00:00
|
|
|
fileExtension, allowed := urlHasExt(url, "jpg", "jpeg", "png")
|
2021-04-21 17:47:45 +00:00
|
|
|
if !allowed {
|
|
|
|
return "", nil
|
|
|
|
}
|
|
|
|
// Compress
|
|
|
|
j, _ := json.Marshal(map[string]interface{}{
|
|
|
|
"source": map[string]interface{}{
|
|
|
|
"url": url,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
req, err := http.NewRequest(http.MethodPost, "https://api.tinify.com/shrink", bytes.NewReader(j))
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2021-06-20 13:18:02 +00:00
|
|
|
req.SetBasicAuth("api", tf.key)
|
2021-06-18 12:32:03 +00:00
|
|
|
req.Header.Set(contentType, contenttype.JSON)
|
2021-06-20 13:18:02 +00:00
|
|
|
resp, err := hc.Do(req)
|
2021-04-21 17:47:45 +00:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
_, _ = io.Copy(io.Discard, resp.Body)
|
|
|
|
if resp.StatusCode != http.StatusCreated {
|
|
|
|
return "", fmt.Errorf("failed to compress image, status code %d", resp.StatusCode)
|
|
|
|
}
|
|
|
|
compressedLocation := resp.Header.Get("Location")
|
|
|
|
if compressedLocation == "" {
|
|
|
|
return "", errors.New("tinify didn't return compressed location")
|
|
|
|
}
|
|
|
|
// Resize and download image
|
|
|
|
j, _ = json.Marshal(map[string]interface{}{
|
|
|
|
"resize": map[string]interface{}{
|
|
|
|
"method": "fit",
|
|
|
|
"width": defaultCompressionWidth,
|
|
|
|
"height": defaultCompressionHeight,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
downloadReq, err := http.NewRequest(http.MethodPost, compressedLocation, bytes.NewReader(j))
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2021-06-20 13:18:02 +00:00
|
|
|
downloadReq.SetBasicAuth("api", tf.key)
|
2021-06-18 12:32:03 +00:00
|
|
|
downloadReq.Header.Set(contentType, contenttype.JSON)
|
2021-06-20 13:18:02 +00:00
|
|
|
downloadResp, err := hc.Do(downloadReq)
|
2021-04-21 17:47:45 +00:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
defer downloadResp.Body.Close()
|
|
|
|
if downloadResp.StatusCode != http.StatusOK {
|
|
|
|
_, _ = io.Copy(io.Discard, downloadResp.Body)
|
|
|
|
return "", fmt.Errorf("tinify failed to resize image, status code %d", downloadResp.StatusCode)
|
|
|
|
}
|
|
|
|
tmpFile, err := os.CreateTemp("", "tiny-*."+fileExtension)
|
|
|
|
if err != nil {
|
|
|
|
_, _ = io.Copy(io.Discard, downloadResp.Body)
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
_ = tmpFile.Close()
|
|
|
|
_ = os.Remove(tmpFile.Name())
|
|
|
|
}()
|
|
|
|
if _, err = io.Copy(tmpFile, downloadResp.Body); err != nil {
|
|
|
|
_, _ = io.Copy(io.Discard, downloadResp.Body)
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
fileName, err := getSHA256(tmpFile)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
// Upload compressed file
|
2021-06-20 13:18:02 +00:00
|
|
|
location, err = upload(fileName+"."+fileExtension, tmpFile)
|
2021-04-21 17:47:45 +00:00
|
|
|
return
|
|
|
|
}
|
2021-04-21 18:10:32 +00:00
|
|
|
|
2021-06-23 12:28:51 +00:00
|
|
|
type cloudflare struct {
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cf *cloudflare) compress(url string, upload mediaStorageSaveFunc, hc httpClient) (location string, err error) {
|
2021-04-21 18:10:32 +00:00
|
|
|
// Check url
|
2021-06-20 13:18:02 +00:00
|
|
|
_, allowed := urlHasExt(url, "jpg", "jpeg", "png")
|
2021-04-21 18:10:32 +00:00
|
|
|
if !allowed {
|
|
|
|
return "", nil
|
|
|
|
}
|
|
|
|
// Force jpeg
|
|
|
|
fileExtension := "jpeg"
|
|
|
|
// Compress
|
|
|
|
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("https://www.cloudflare.com/cdn-cgi/image/f=jpeg,q=75,metadata=none,fit=scale-down,w=%d,h=%d/%s", defaultCompressionWidth, defaultCompressionHeight, url), nil)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2021-06-20 13:18:02 +00:00
|
|
|
resp, err := hc.Do(req)
|
2021-04-21 18:10:32 +00:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
|
|
_, _ = io.Copy(io.Discard, resp.Body)
|
|
|
|
return "", fmt.Errorf("cloudflare failed to compress image, status code %d", resp.StatusCode)
|
|
|
|
}
|
|
|
|
tmpFile, err := os.CreateTemp("", "tiny-*."+fileExtension)
|
|
|
|
if err != nil {
|
|
|
|
_, _ = io.Copy(io.Discard, resp.Body)
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
_ = tmpFile.Close()
|
|
|
|
_ = os.Remove(tmpFile.Name())
|
|
|
|
}()
|
|
|
|
if _, err = io.Copy(tmpFile, resp.Body); err != nil {
|
|
|
|
_, _ = io.Copy(io.Discard, resp.Body)
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
fileName, err := getSHA256(tmpFile)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
// Upload compressed file
|
2021-06-20 13:18:02 +00:00
|
|
|
location, err = upload(fileName+"."+fileExtension, tmpFile)
|
2021-04-21 18:10:32 +00:00
|
|
|
return
|
|
|
|
}
|