mirror of
https://github.com/mickael-kerjean/filestash.git
synced 2025-10-30 09:37:55 +08:00
161 lines
4.6 KiB
Go
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
|
|
}
|