Files
2024-12-22 03:06:48 +11:00

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
}