mirror of
https://github.com/mickael-kerjean/filestash.git
synced 2025-10-30 01:26:43 +08:00
135 lines
4.6 KiB
Go
135 lines
4.6 KiB
Go
package plg_image_c
|
|
|
|
import (
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
|
|
. "github.com/mickael-kerjean/filestash/server/common"
|
|
)
|
|
|
|
/*
|
|
* All the transcoders are reponsible for:
|
|
* 1. create thumbnails if needed
|
|
* 2. transcode various files if needed
|
|
*
|
|
* Under the hood, our transcoders are C programs that takes 3 arguments:
|
|
* 1/2. the input/output file descriptors. We use file descriptors to communicate from go -> C -> go
|
|
* 3. the target size. by convention those program handles:
|
|
* - positive size: when we want to transcode a file with best effort in regards to quality and
|
|
* not lose metadata, typically when this will be open in an image viewer from which we might have
|
|
* frontend code to extract exif/xmp metadata, ...
|
|
* - negative size: when we want transcode to be done as quickly as possible, typically when we want
|
|
* to create a thumbnail and don't care/need anything else than speed
|
|
*/
|
|
|
|
func init() {
|
|
Hooks.Register.Thumbnailer("image/jpeg", &transcoder{runner(jpeg), "image/jpeg", -200})
|
|
Hooks.Register.Thumbnailer("image/png", &transcoder{runner(png), "image/webp", -200})
|
|
Hooks.Register.Thumbnailer("image/gif", &transcoder{runner(gif), "image/webp", -300})
|
|
Hooks.Register.Thumbnailer("image/heic", &transcoder{runner(heif), "image/jpeg", -200})
|
|
Hooks.Register.Thumbnailer("image/webp", &transcoder{runner(webp), "image/webp", -200})
|
|
Hooks.Register.Thumbnailer("image/tiff", &transcoder{runner(tiff), "image/webp", -200})
|
|
rawMimeType := []string{
|
|
"image/x-canon-cr2", "image/x-tif", "image/x-canon-cr2", "image/x-canon-crw",
|
|
"image/x-nikon-nef", "image/x-nikon-nrw", "image/x-sony-arw", "image/x-sony-sr2",
|
|
"image/x-minolta-mrw", "image/x-minolta-mdc", "image/x-olympus-orf", "image/x-panasonic-rw2",
|
|
"image/x-pentax-pef", "image/x-epson-erf", "image/x-raw", "image/x-x3f", "image/x-fuji-raf",
|
|
"image/x-aptus-mos", "image/x-mamiya-mef", "image/x-hasselblad-3fr", "image/x-adobe-dng",
|
|
"image/x-samsung-srw", "image/x-kodak-kdc", "image/x-kodak-dcr",
|
|
}
|
|
for _, mType := range rawMimeType {
|
|
Hooks.Register.Thumbnailer(mType, &transcoder{runner(raw), "image/jpeg", -200})
|
|
}
|
|
|
|
Hooks.Register.ProcessFileContentBeforeSend(func(reader io.ReadCloser, ctx *App, res *http.ResponseWriter, req *http.Request) (io.ReadCloser, error) {
|
|
query := req.URL.Query()
|
|
mType := GetMimeType(query.Get("path"))
|
|
if strings.HasPrefix(mType, "image/") == false {
|
|
return reader, nil
|
|
} else if query.Get("thumbnail") == "true" {
|
|
return reader, nil
|
|
} else if query.Get("size") == "" {
|
|
return reader, nil
|
|
}
|
|
sizeInt, err := strconv.Atoi(query.Get("size"))
|
|
if err != nil {
|
|
return reader, nil
|
|
}
|
|
if mType == "image/heic" {
|
|
return transcoder{runner(heif), "image/jpeg", sizeInt}.
|
|
Generate(reader, ctx, res, req)
|
|
} else if contains(rawMimeType, mType) {
|
|
return transcoder{runner(raw), "image/jpeg", sizeInt}.
|
|
Generate(reader, ctx, res, req)
|
|
}
|
|
return reader, nil
|
|
})
|
|
}
|
|
|
|
type transcoder struct {
|
|
fn func(input io.ReadCloser, size int) (io.ReadCloser, error)
|
|
mime string
|
|
size int
|
|
}
|
|
|
|
func (this transcoder) Generate(reader io.ReadCloser, ctx *App, res *http.ResponseWriter, req *http.Request) (io.ReadCloser, error) {
|
|
thumb, err := this.fn(reader, this.size)
|
|
if err == nil && this.mime != "" {
|
|
(*res).Header().Set("Content-Type", this.mime)
|
|
}
|
|
return thumb, err
|
|
}
|
|
|
|
/*
|
|
* uuuh, what is this stuff you might rightly wonder? Trying to send a go stream to C isn't obvious,
|
|
* but if you try to stream from C back to go in the same time, this is what you endup with.
|
|
* To my knowledge using file descriptor is the best way we can do that if we don't make the assumption
|
|
* that everything fits in memory.
|
|
*/
|
|
func runner(fn func(uintptr, uintptr, int)) func(io.ReadCloser, int) (io.ReadCloser, error) {
|
|
return func(inputGo io.ReadCloser, size int) (io.ReadCloser, error) {
|
|
inputC, tmpw, err := os.Pipe()
|
|
logErrors(err, "plg_image_c::pipe")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
outputGo, outputC, err := os.Pipe()
|
|
logErrors(err, "plg_image_c::pipe")
|
|
if err != nil {
|
|
tmpw.Close()
|
|
return nil, err
|
|
}
|
|
|
|
go func() {
|
|
fn(inputC.Fd(), outputC.Fd(), size) // <-- all this code so we can do that
|
|
logErrors(inputC.Close(), "plg_image_c::inputC")
|
|
logErrors(inputGo.Close(), "plg_image_c::inputGo")
|
|
logErrors(outputC.Close(), "plg_image_c::outputC")
|
|
}()
|
|
go func() {
|
|
io.Copy(tmpw, inputGo)
|
|
logErrors(tmpw.Close(), "plg_image_c::tmpw")
|
|
}()
|
|
return outputGo, nil
|
|
}
|
|
}
|
|
|
|
func logErrors(err error, msg string) {
|
|
if err == nil {
|
|
return
|
|
}
|
|
Log.Debug(msg + ": " + err.Error())
|
|
}
|
|
|
|
func contains(s []string, str string) bool {
|
|
for _, v := range s {
|
|
if v == str {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|