Files
2022-12-22 22:13:52 +11:00

161 lines
4.6 KiB
Go

package plg_image_thumbnail
import (
"bytes"
_ "embed"
"errors"
"fmt"
. "github.com/mickael-kerjean/filestash/server/common"
"io"
"net/http"
"os"
"os/exec"
"sync"
"time"
)
//go:embed dist/placeholder.png
var placeholder []byte
func init() {
Hooks.Register.Thumbnailer("image/png", thumbnailBuilder{thumbnailPng})
Hooks.Register.Thumbnailer("image/jpeg", thumbnailBuilder{thumbnailJpeg})
for _, mType := range []string{
"image/x-canon-cr2", "image/x-fuji-raf", "image/x-nikon-nef",
"image/x-nikon-nrw", "image/x-epson-erf",
} {
Hooks.Register.Thumbnailer(mType, thumbnailBuilder{thumbnailRaw})
}
Hooks.Register.ProcessFileContentBeforeSend(renderRaw)
}
func thumbnailPng(reader io.ReadCloser, ctx *App, res *http.ResponseWriter, req *http.Request) (io.ReadCloser, error) {
h := (*res).Header()
r, err := createThumbnailForPng(reader)
if err != nil {
h.Set("Content-Type", "image/png")
h.Set("Cache-Control", "max-age=1")
return NewReadCloserFromBytes(placeholder), nil
}
h.Set("Content-Type", "image/webp")
h.Set("Cache-Control", fmt.Sprintf("max-age=%d", 3600*12))
return r, nil
}
func thumbnailJpeg(reader io.ReadCloser, ctx *App, res *http.ResponseWriter, req *http.Request) (io.ReadCloser, error) {
h := (*res).Header()
r, err := createThumbnailForJpeg(reader)
if err != nil {
h.Set("Content-Type", "image/png")
h.Set("Cache-Control", "max-age=1")
return NewReadCloserFromBytes(placeholder), nil
}
h.Set("Content-Type", "image/jpeg")
h.Set("Cache-Control", fmt.Sprintf("max-age=%d", 3600*12))
return r, nil
}
func thumbnailRaw(reader io.ReadCloser, ctx *App, res *http.ResponseWriter, req *http.Request) (io.ReadCloser, error) {
h := (*res).Header()
r, err := createThumbnailForRaw(reader)
if err != nil {
h.Set("Content-Type", "image/png")
h.Set("Cache-Control", "max-age=1")
return NewReadCloserFromBytes(placeholder), nil
}
h.Set("Content-Type", "image/jpeg")
h.Set("Cache-Control", fmt.Sprintf("max-age=%d", 3600*12))
return r, nil
}
func renderRaw(reader io.ReadCloser, ctx *App, res *http.ResponseWriter, req *http.Request) (io.ReadCloser, error) {
query := req.URL.Query()
if query.Get("thumbnail") == "true" {
return reader, nil
} else if isRaw(GetMimeType(query.Get("path"))) == false {
return reader, nil
} else if query.Get("size") == "" {
return reader, nil
}
h := (*res).Header()
r, err := createRenderingForRaw(reader, query.Get("size"))
if err != nil {
h.Set("Content-Type", "image/png")
return NewReadCloserFromBytes(placeholder), nil
}
h.Set("Content-Type", "image/jpeg")
h.Set("Cache-Control", fmt.Sprintf("max-age=%d", 3600*12))
return r, nil
}
type thumbnailBuilder struct {
fn func(reader io.ReadCloser, ctx *App, res *http.ResponseWriter, req *http.Request) (io.ReadCloser, error)
}
func (this thumbnailBuilder) Generate(reader io.ReadCloser, ctx *App, res *http.ResponseWriter, req *http.Request) (io.ReadCloser, error) {
return this.fn(reader, ctx, res, req)
}
type ThumbnailExecutable struct {
Name string
Binary *[]byte
Checksum []byte
isValid bool
lastVerify time.Time
sync.Mutex
}
func (this ThumbnailExecutable) Init() {
p := "/tmp/" + this.Name
f, err := os.OpenFile(p, os.O_RDONLY, os.ModePerm)
if err != nil {
outFile, err := os.OpenFile(p, os.O_CREATE|os.O_WRONLY, os.ModePerm)
if err != nil {
Log.Warning("plg_image_thumbnail::init::run::openFile '%s'", this.Name)
return
}
outFile.Write(*this.Binary)
if err = outFile.Close(); err != nil {
Log.Warning("plg_image_thumbnail::init::run::close '%s'", this.Name)
return
}
}
f.Close()
}
func (this *ThumbnailExecutable) verify() bool {
this.Lock()
defer this.Unlock()
if time.Since(this.lastVerify) > 30*time.Second {
this.lastVerify = time.Now()
f, err := os.OpenFile("/tmp/"+this.Name, os.O_RDONLY, os.ModePerm)
if err == nil && bytes.Equal([]byte(HashStream(f, 0)), this.Checksum) {
this.isValid = true
}
}
return this.isValid
}
func (this *ThumbnailExecutable) Execute(reader io.ReadCloser, params ...string) (io.ReadCloser, error) {
if this.verify() == false {
Log.Error("plg_image_thumbnail::execution abort after verification on '%s'", this.Name)
reader.Close()
return nil, ErrFilesystemError
}
var buf bytes.Buffer
var errBuff bytes.Buffer
cmd := exec.Command("/tmp/"+this.Name, params...)
cmd.Stdin = reader
cmd.Stdout = &buf
cmd.Stderr = &errBuff
if err := cmd.Run(); err != nil {
reader.Close()
Log.Debug("plg_image_thumbmail::resize %s ERR %s", this.Name, string(errBuff.Bytes()))
return nil, errors.New(string(errBuff.Bytes()))
}
cmd.Wait()
reader.Close()
return NewReadCloserFromBytes(buf.Bytes()), nil
}