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/webp", &transcoder{runner(webp), "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 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 }