diff --git a/go.mod b/go.mod index cfc64d84..7c45929c 100644 --- a/go.mod +++ b/go.mod @@ -25,6 +25,8 @@ require ( github.com/rasky/go-xdr v0.0.0-20170124162913-1a41d1a06c93 // indirect github.com/rogpeppe/go-internal v1.8.0 // indirect github.com/secsy/goftp v0.0.0-20200609142545-aa2de14babf4 // indirect + github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c + github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef github.com/suyashkumar/dicom v1.0.5 github.com/tidwall/gjson v1.13.0 github.com/tidwall/sjson v1.0.4 @@ -32,8 +34,8 @@ require ( github.com/vmware/go-nfs-client v0.0.0-20190605212624-d43b92724c1b github.com/wayneashleyberry/terminal-dimensions v1.1.0 // indirect golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838 - golang.org/x/image v0.0.0-20210622092929-e6eecd499c2c - golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 + golang.org/x/image v0.0.0-20211028202545-6944b10bf410 + golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 diff --git a/go.sum b/go.sum index 202acd68..299ae8c8 100644 --- a/go.sum +++ b/go.sum @@ -245,6 +245,11 @@ github.com/spacemonkeygo/monkit/v3 v3.0.17/go.mod h1:kj1ViJhlyADa7DiA4xVnTuPA46l github.com/spacemonkeygo/monotime v0.0.0-20180824235756-e3f48a95f98a/go.mod h1:ul4bvvnCOPZgq8w0nTkSmWVg/hauVpFS97Am1YM1XXo= github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4= github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI= +github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c h1:km8GpoQut05eY3GiYWEedbTT0qnSxrCjsVbb7yKY1KE= +github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q= +github.com/srwiley/rasterx v0.0.0-20210519020934-456a8d69b780/go.mod h1:mvWM0+15UqyrFKqdRjY6LuAVJR0HOVhJlEgZ5JWtSWU= +github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqdkI8FSpFyZDtCVBc2VmejdNrm5rRQ= +github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= @@ -310,6 +315,8 @@ golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/image v0.0.0-20210622092929-e6eecd499c2c h1:FRR4fGZm/CMwZka5baQ4z8c8StbxJOMjS/45e0BAxK0= golang.org/x/image v0.0.0-20210622092929-e6eecd499c2c/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/image v0.0.0-20211028202545-6944b10bf410 h1:hTftEOvwiOq2+O8k2D5/Q7COC7k5Qcrgc2TFURJYnvQ= +golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -343,6 +350,8 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4 h1:DZshvxDdVoeKIbudAdFEKi+f70l51luSy/7b76ibTY0= +golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= diff --git a/server/plugin/index.go b/server/plugin/index.go index 02c27d48..9e514a15 100644 --- a/server/plugin/index.go +++ b/server/plugin/index.go @@ -28,8 +28,8 @@ import ( _ "github.com/mickael-kerjean/filestash/server/plugin/plg_editor_onlyoffice" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_handler_console" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_image_ascii" - _ "github.com/mickael-kerjean/filestash/server/plugin/plg_image_dicom" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_image_thumbnail" + _ "github.com/mickael-kerjean/filestash/server/plugin/plg_image_transcode" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_search_stateless" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_security_scanner" _ "github.com/mickael-kerjean/filestash/server/plugin/plg_security_svg" diff --git a/server/plugin/plg_image_dicom/index.go b/server/plugin/plg_image_dicom/index.go deleted file mode 100644 index 1d6e3dfa..00000000 --- a/server/plugin/plg_image_dicom/index.go +++ /dev/null @@ -1,64 +0,0 @@ -package plg_image_dicom - -import ( - "bufio" - "bytes" - . "github.com/mickael-kerjean/filestash/server/common" - "github.com/suyashkumar/dicom" - "github.com/suyashkumar/dicom/pkg/tag" - "image/jpeg" - "io" - "net/http" -) - -func init() { - Hooks.Register.ProcessFileContentBeforeSend(renderDicom) -} - -func renderDicom(reader io.ReadCloser, ctx *App, res *http.ResponseWriter, req *http.Request) (io.ReadCloser, error) { - query := req.URL.Query() - if query.Get("size") == "" { - return reader, nil - } else if GetMimeType(query.Get("path")) != "image/dicom" { - return reader, nil - } - var b bytes.Buffer - w := bufio.NewWriter(&b) - io.Copy(w, reader) - reader.Close() - dataset, err := dicom.Parse(&b, int64(len(b.Bytes())), nil) - if err != nil { - Log.Debug("plg_image_dicom::parse '%s'", err.Error()) - return nil, ErrNotValid - } - pixelDataElement, err := dataset.FindElementByTag(tag.PixelData) - if err != nil { - Log.Debug("plg_image_dicom::findElementByTag '%s'", err.Error()) - return nil, ErrNotValid - } - pixelDataInfo := dicom.MustGetPixelDataInfo(pixelDataElement.Value) - - for _, fr := range pixelDataInfo.Frames { - img, err := fr.GetImage() - if err != nil { - if err.Error() == "unsupported JPEG feature: unknown marker" { - // known issue with lossless jpeg codec which isn't supported in golang - // and is not trivial to support in Filestash - return nil, ErrNotImplemented - } - Log.Stdout("plg_image_dicom::getImage '%s'", err.Error()) - return nil, err - } - var b bytes.Buffer - w := bufio.NewWriter(&b) - err = jpeg.Encode(w, img, &jpeg.Options{Quality: 100}) - if err != nil { - Log.Debug("plg_image_dicom::encode '%s'", err.Error()) - return nil, err - } - h := (*res).Header() - h.Set("Content-Type", "image/jpeg") - return NewReadCloserFromReader(&b), nil - } - return nil, ErrNotValid -} diff --git a/server/plugin/plg_image_thumbnail/index.go b/server/plugin/plg_image_thumbnail/index.go index 00977d37..a6306a7b 100644 --- a/server/plugin/plg_image_thumbnail/index.go +++ b/server/plugin/plg_image_thumbnail/index.go @@ -23,9 +23,6 @@ func init() { for _, mType := range []string{ "image/x-canon-cr2", "image/x-fuji-raf", "image/x-nikon-nef", "image/x-nikon-nrw", "image/x-epson-erf", - // "image/tiff", - // "image/x-kodak-dcr", "image/x-hasselblad-3fr", - // "image/x-raw", } { Hooks.Register.Thumbnailer(mType, thumbnailBuilder{thumbnailRaw}) } diff --git a/server/plugin/plg_image_transcode/index.go b/server/plugin/plg_image_transcode/index.go new file mode 100644 index 00000000..3f0b481d --- /dev/null +++ b/server/plugin/plg_image_transcode/index.go @@ -0,0 +1,45 @@ +package plg_image_transcode + +import ( + . "github.com/mickael-kerjean/filestash/server/common" + "io" + "net/http" +) + +func init() { + Hooks.Register.ProcessFileContentBeforeSend(renderImages) +} + +func renderImages(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 query.Get("size") == "" { + return reader, nil + } + + var ( + out io.ReadCloser = nil + err error = nil + ) + mType := GetMimeType(query.Get("path")) + switch mType { + case "image/x-ms-bmp": + out, mType, err = transcodeBmp(reader) + case "image/tiff": + out, mType, err = transcodeTiff(reader) + case "image/dicom": + out, mType, err = transcodeDicom(reader) + default: + err = ErrNotImplemented + } + reader.Close() + if err == nil { + (*res).Header().Set("Content-Type", mType) + } + if err != nil && err != ErrNotImplemented && err != ErrNotValid { + Log.Debug("plg_image_transcode::err %s", err.Error()) + return nil, ErrNotValid + } + return out, err +} diff --git a/server/plugin/plg_image_transcode/transcode_bmp.go b/server/plugin/plg_image_transcode/transcode_bmp.go new file mode 100644 index 00000000..131efb7d --- /dev/null +++ b/server/plugin/plg_image_transcode/transcode_bmp.go @@ -0,0 +1,26 @@ +package plg_image_transcode + +import ( + . "github.com/mickael-kerjean/filestash/server/common" + _ "golang.org/x/image/bmp" + "image" + "image/jpeg" + "io" +) + +func transcodeBmp(reader io.Reader) (io.ReadCloser, string, error) { + img, _, err := image.Decode(reader) + if err != nil { + return nil, "", err + } + + r, w := io.Pipe() + go func() { + err := jpeg.Encode(w, img, &jpeg.Options{Quality: 80}) + w.Close() + if err != nil { + Log.Debug("plg_image_transcode::bmp jpeg encoding error '%s'", err.Error()) + } + }() + return NewReadCloserFromReader(r), "image/jpeg", nil +} diff --git a/server/plugin/plg_image_transcode/transcode_dicom.go b/server/plugin/plg_image_transcode/transcode_dicom.go new file mode 100644 index 00000000..9e517628 --- /dev/null +++ b/server/plugin/plg_image_transcode/transcode_dicom.go @@ -0,0 +1,53 @@ +package plg_image_transcode + +import ( + "bufio" + "bytes" + . "github.com/mickael-kerjean/filestash/server/common" + "github.com/suyashkumar/dicom" + "github.com/suyashkumar/dicom/pkg/tag" + "image/jpeg" + "io" +) + +func transcodeDicom(reader io.Reader) (io.ReadCloser, string, error) { + var b bytes.Buffer + w := bufio.NewWriter(&b) + io.Copy(w, reader) + + dataset, err := dicom.Parse(&b, int64(len(b.Bytes())), nil) + if err != nil { + Log.Debug("plg_image_transcode::dicom::parse '%s'", err.Error()) + return nil, "", ErrNotValid + } + pixelDataElement, err := dataset.FindElementByTag(tag.PixelData) + if err != nil { + Log.Debug("plg_image_transcode::dicom::findElementByTag '%s'", err.Error()) + return nil, "", ErrNotValid + } + pixelDataInfo := dicom.MustGetPixelDataInfo(pixelDataElement.Value) + + for _, fr := range pixelDataInfo.Frames { + img, err := fr.GetImage() + if err != nil { + if err.Error() == "unsupported JPEG feature: unknown marker" { + // known issue with lossless jpeg codec which isn't supported in golang + // and is not trivial to support in Filestash + return nil, "", ErrNotImplemented + } + Log.Stdout("plg_image_transcode_dicom::getImage '%s'", err.Error()) + return nil, "", err + } + + r, w := io.Pipe() + go func() { + err := jpeg.Encode(w, img, &jpeg.Options{Quality: 80}) + w.Close() + if err != nil { + Log.Debug("plg_image_transcode::dicom jpeg encoding error '%s'", err.Error()) + } + }() + return NewReadCloserFromReader(r), "image/jpeg", nil + } + return nil, "", ErrNotValid +} diff --git a/server/plugin/plg_image_transcode/transcode_svg.go b/server/plugin/plg_image_transcode/transcode_svg.go new file mode 100644 index 00000000..39d35c3f --- /dev/null +++ b/server/plugin/plg_image_transcode/transcode_svg.go @@ -0,0 +1,41 @@ +package plg_image_transcode + +import ( + . "github.com/mickael-kerjean/filestash/server/common" + "github.com/srwiley/oksvg" + "github.com/srwiley/rasterx" + "image" + "image/png" + "io" +) + +/* + * This bit isn't used because the rendering is very poor and would + * generate too many bug reports + */ +func transcodeSvg(reader io.Reader) (io.ReadCloser, string, error) { + icon, err := oksvg.ReadIconStream(reader) + if err != nil { + return nil, "", err + } + icon.SetTarget(0, 0, icon.ViewBox.W, icon.ViewBox.H) + width := int(icon.ViewBox.W) + height := int(icon.ViewBox.H) + img := image.NewRGBA(image.Rect(0, 0, width, height)) + icon.Draw( + rasterx.NewDasher( + width, height, + rasterx.NewScannerGV(width, height, img, img.Bounds()), + ), 1, + ) + + r, w := io.Pipe() + go func() { + err := png.Encode(w, img) + w.Close() + if err != nil { + Log.Debug("plg_image_transcode::svg png encoding error '%s'", err.Error()) + } + }() + return NewReadCloserFromReader(r), "image/png", nil +} diff --git a/server/plugin/plg_image_transcode/transcode_tiff.go b/server/plugin/plg_image_transcode/transcode_tiff.go new file mode 100644 index 00000000..66b7dc75 --- /dev/null +++ b/server/plugin/plg_image_transcode/transcode_tiff.go @@ -0,0 +1,26 @@ +package plg_image_transcode + +import ( + . "github.com/mickael-kerjean/filestash/server/common" + _ "golang.org/x/image/tiff" + "image" + "image/jpeg" + "io" +) + +func transcodeTiff(reader io.Reader) (io.ReadCloser, string, error) { + img, _, err := image.Decode(reader) + if err != nil { + return nil, "", err + } + + r, w := io.Pipe() + go func() { + err := jpeg.Encode(w, img, &jpeg.Options{Quality: 80}) + w.Close() + if err != nil { + Log.Debug("plg_image_transcode::tiff jpeg encoding error '%s'", err.Error()) + } + }() + return NewReadCloserFromReader(r), "image/jpeg", nil +}