mirror of
				https://github.com/mickael-kerjean/filestash.git
				synced 2025-10-31 01:58:11 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			127 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			127 lines
		
	
	
		
			4.2 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/heic", &transcoder{runner(heif), "image/jpeg", -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()
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		outputGo, outputC, err := os.Pipe()
 | |
| 		if err != nil {
 | |
| 			tmpw.Close()
 | |
| 			Log.Stdout("ERR0 %+v", err.Error())
 | |
| 			return nil, err
 | |
| 		}
 | |
| 
 | |
| 		go func() {
 | |
| 			fn(inputC.Fd(), outputC.Fd(), size) // <-- all this code so we can do that
 | |
| 			inputC.Close()
 | |
| 			outputC.Close()
 | |
| 		}()
 | |
| 		_, err = io.Copy(tmpw, inputGo)
 | |
| 		inputGo.Close()
 | |
| 		tmpw.Close()
 | |
| 		if err != nil {
 | |
| 			outputGo.Close()
 | |
| 			Log.Stdout("ERR1 %+v", err.Error())
 | |
| 			return nil, err
 | |
| 		}
 | |
| 		return outputGo, nil
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func contains(s []string, str string) bool {
 | |
| 	for _, v := range s {
 | |
| 		if v == str {
 | |
| 			return true
 | |
| 		}
 | |
| 	}
 | |
| 	return false
 | |
| }
 | 
