GoBlog/mediaCompression.go

188 lines
5.0 KiB
Go
Raw Permalink Normal View History

2021-04-21 17:47:45 +00:00
package main
import (
"context"
2022-02-11 18:39:07 +00:00
"crypto/sha256"
2021-04-21 17:47:45 +00:00
"errors"
"fmt"
"image/png"
2022-02-11 18:39:07 +00:00
"io"
2021-04-21 17:47:45 +00:00
"net/http"
"github.com/carlmjohnson/requests"
"github.com/disintegration/imaging"
2022-02-11 18:39:07 +00:00
"go.goblog.app/app/pkgs/bufferpool"
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 {
compress(url string, save mediaStorageSaveFunc, hc *http.Client) (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 {
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() {
if a.cfg.Micropub == nil || a.cfg.Micropub.MediaStorage == nil {
2021-06-20 13:18:02 +00:00
return
}
config := a.cfg.Micropub.MediaStorage
2021-06-20 13:18:02 +00:00
if key := config.TinifyKey; key != "" {
2023-12-27 10:37:58 +00:00
a.compressors = append(a.compressors, &tinify{a: a, key: key})
2021-06-20 13:18:02 +00:00
}
if config.CloudflareCompressionEnabled {
a.compressors = append(a.compressors, &cloudflare{})
}
2022-03-24 14:19:34 +00:00
if config.LocalCompressionEnabled {
2023-12-27 10:37:58 +00:00
a.compressors = append(a.compressors, &localMediaCompressor{a: a})
2022-03-24 14:19:34 +00:00
}
2021-06-20 13:18:02 +00:00
}
type tinify struct {
2023-12-27 10:37:58 +00:00
a *goBlog
key string
}
func (tf *tinify) compress(url string, upload mediaStorageSaveFunc, hc *http.Client) (string, error) {
2022-01-04 09:37:48 +00:00
tinifyErr := errors.New("failed to compress image using tinify")
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
2022-01-04 09:37:48 +00:00
headers := http.Header{}
err := requests.
URL("https://api.tinify.com/shrink").
Client(hc).
2022-01-04 08:48:37 +00:00
Method(http.MethodPost).
BasicAuth("api", tf.key).
2022-03-16 07:28:03 +00:00
BodyJSON(map[string]any{
"source": map[string]any{
"url": url,
},
}).
2022-01-04 09:37:48 +00:00
ToHeaders(headers).
Fetch(context.Background())
2021-04-21 17:47:45 +00:00
if err != nil {
2023-12-27 10:37:58 +00:00
tf.a.error("Tinify error", "err", err)
2022-01-04 09:37:48 +00:00
return "", tinifyErr
}
compressedLocation := headers.Get("Location")
if compressedLocation == "" {
2023-12-27 10:37:58 +00:00
tf.a.error("Tinify error: location header missing")
2022-01-04 09:37:48 +00:00
return "", tinifyErr
2021-04-21 17:47:45 +00:00
}
// Resize and download image
pr, pw := io.Pipe()
go func() {
_ = pw.CloseWithError(requests.
URL(compressedLocation).
Client(hc).
Method(http.MethodPost).
BasicAuth("api", tf.key).
BodyJSON(map[string]any{
"resize": map[string]any{
"method": "fit",
"width": defaultCompressionWidth,
"height": defaultCompressionHeight,
},
}).
ToWriter(pw).
Fetch(context.Background()))
}()
2021-04-21 17:47:45 +00:00
// Upload compressed file
res, err := uploadCompressedFile(fileExtension, pr, upload)
_ = pr.CloseWithError(err)
return res, err
2021-04-21 17:47:45 +00:00
}
type cloudflare struct{}
2022-02-25 15:29:42 +00:00
func (*cloudflare) compress(url string, upload mediaStorageSaveFunc, hc *http.Client) (string, error) {
// Check url
if _, allowed := urlHasExt(url, "jpg", "jpeg", "png"); !allowed {
return "", nil
}
// Force jpeg
fileExtension := "jpeg"
// Compress
pr, pw := io.Pipe()
go func() {
_ = pw.CloseWithError(requests.
URL(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)).
Client(hc).
ToWriter(pw).
Fetch(context.Background()))
}()
// Upload compressed file
res, err := uploadCompressedFile(fileExtension, pr, upload)
_ = pr.CloseWithError(err)
return res, err
}
2023-12-27 10:37:58 +00:00
type localMediaCompressor struct {
a *goBlog
}
2022-03-24 14:19:34 +00:00
2023-12-27 10:37:58 +00:00
func (lc *localMediaCompressor) compress(url string, upload mediaStorageSaveFunc, hc *http.Client) (string, error) {
2022-03-24 14:19:34 +00:00
// Check url
fileExtension, allowed := urlHasExt(url, "jpg", "jpeg", "png")
if !allowed {
return "", nil
}
// Download and decode image
pr, pw := io.Pipe()
go func() {
_ = pw.CloseWithError(requests.URL(url).Client(hc).ToWriter(pw).Fetch(context.Background()))
}()
img, err := imaging.Decode(pr, imaging.AutoOrientation(true))
_ = pr.CloseWithError(err)
2022-03-24 14:19:34 +00:00
if err != nil {
2023-12-27 10:37:58 +00:00
lc.a.error("Local compressor error", "err", err)
2022-03-24 14:19:34 +00:00
return "", errors.New("failed to compress image using local compressor")
}
// Resize image
resizedImage := imaging.Fit(img, defaultCompressionWidth, defaultCompressionHeight, imaging.Lanczos)
// Encode image
pr, pw = io.Pipe()
go func() {
switch fileExtension {
case "png":
_ = pw.CloseWithError(imaging.Encode(pw, resizedImage, imaging.PNG, imaging.PNGCompressionLevel(png.BestCompression)))
default:
_ = pw.CloseWithError(imaging.Encode(pw, resizedImage, imaging.JPEG, imaging.JPEGQuality(75)))
}
}()
2022-03-24 14:19:34 +00:00
// Upload compressed file
res, err := uploadCompressedFile(fileExtension, pr, upload)
_ = pr.CloseWithError(err)
return res, err
2022-03-24 14:19:34 +00:00
}
2022-02-11 18:39:07 +00:00
func uploadCompressedFile(fileExtension string, r io.Reader, upload mediaStorageSaveFunc) (string, error) {
// Copy file to temporary buffer to generate hash and filename
hash := sha256.New()
tempBuffer := bufferpool.Get()
defer bufferpool.Put(tempBuffer)
_, err := io.Copy(io.MultiWriter(tempBuffer, hash), r)
if err != nil {
return "", err
}
2022-02-11 18:39:07 +00:00
// Upload buffer
return upload(fmt.Sprintf("%x.%s", hash.Sum(nil), fileExtension), tempBuffer)
}