You've already forked filebrowser
mirror of
https://github.com/filebrowser/filebrowser.git
synced 2025-11-26 14:25:26 +08:00
feat: add EXIF thumbnail support for JPEG files (#1234)
This commit is contained in:
@@ -10,7 +10,10 @@ import (
|
||||
"io"
|
||||
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/dsoprea/go-exif/v3"
|
||||
"github.com/marusama/semaphore/v2"
|
||||
|
||||
exifcommon "github.com/dsoprea/go-exif/v3/common"
|
||||
)
|
||||
|
||||
// ErrUnsupportedFormat means the given image format is not supported.
|
||||
@@ -152,6 +155,17 @@ func (s *Service) Resize(ctx context.Context, in io.Reader, width, height int, o
|
||||
option(&config)
|
||||
}
|
||||
|
||||
if config.quality == QualityLow && format == FormatJpeg {
|
||||
thm, newWrappedReader, errThm := getEmbeddedThumbnail(wrappedReader)
|
||||
wrappedReader = newWrappedReader
|
||||
if errThm == nil {
|
||||
_, err = out.Write(thm)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
img, err := imaging.Decode(wrappedReader, imaging.AutoOrientation(true))
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -183,3 +197,46 @@ func (s *Service) detectFormat(in io.Reader) (Format, io.Reader, error) {
|
||||
|
||||
return format, io.MultiReader(buf, in), nil
|
||||
}
|
||||
|
||||
func getEmbeddedThumbnail(in io.Reader) ([]byte, io.Reader, error) {
|
||||
buf := &bytes.Buffer{}
|
||||
r := io.TeeReader(in, buf)
|
||||
wrappedReader := io.MultiReader(buf, in)
|
||||
|
||||
offset := 0
|
||||
offsets := []int{12, 30}
|
||||
head := make([]byte, 0xffff)
|
||||
|
||||
_, err := r.Read(head)
|
||||
if err != nil {
|
||||
return nil, wrappedReader, err
|
||||
}
|
||||
|
||||
for _, offset = range offsets {
|
||||
if _, err = exif.ParseExifHeader(head[offset:]); err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, wrappedReader, err
|
||||
}
|
||||
|
||||
im, err := exifcommon.NewIfdMappingWithStandard()
|
||||
if err != nil {
|
||||
return nil, wrappedReader, err
|
||||
}
|
||||
|
||||
_, index, err := exif.Collect(im, exif.NewTagIndex(), head[offset:])
|
||||
if err != nil {
|
||||
return nil, wrappedReader, err
|
||||
}
|
||||
|
||||
ifd := index.RootIfd.NextIfd()
|
||||
if ifd == nil {
|
||||
return nil, wrappedReader, exif.ErrNoThumbnail
|
||||
}
|
||||
|
||||
thm, err := ifd.Thumbnail()
|
||||
return thm, wrappedReader, err
|
||||
}
|
||||
|
||||
@@ -216,6 +216,46 @@ func TestService_Resize(t *testing.T) {
|
||||
},
|
||||
matcher: sizeMatcher(100, 100),
|
||||
},
|
||||
"get thumbnail from file with APP0 JFIF": {
|
||||
options: []Option{WithMode(ResizeModeFill), WithQuality(QualityLow)},
|
||||
width: 100,
|
||||
height: 100,
|
||||
source: func(t *testing.T) afero.File {
|
||||
t.Helper()
|
||||
return openFile(t, "testdata/gray-sample.jpg")
|
||||
},
|
||||
matcher: sizeMatcher(125, 128),
|
||||
},
|
||||
"get thumbnail from file without APP0 JFIF": {
|
||||
options: []Option{WithMode(ResizeModeFill), WithQuality(QualityLow)},
|
||||
width: 100,
|
||||
height: 100,
|
||||
source: func(t *testing.T) afero.File {
|
||||
t.Helper()
|
||||
return openFile(t, "testdata/20130612_142406.jpg")
|
||||
},
|
||||
matcher: sizeMatcher(320, 240),
|
||||
},
|
||||
"resize from file without IFD1 thumbnail": {
|
||||
options: []Option{WithMode(ResizeModeFill), WithQuality(QualityLow)},
|
||||
width: 100,
|
||||
height: 100,
|
||||
source: func(t *testing.T) afero.File {
|
||||
t.Helper()
|
||||
return openFile(t, "testdata/IMG_2578.JPG")
|
||||
},
|
||||
matcher: sizeMatcher(100, 100),
|
||||
},
|
||||
"resize for higher quality levels": {
|
||||
options: []Option{WithMode(ResizeModeFill), WithQuality(QualityMedium)},
|
||||
width: 100,
|
||||
height: 100,
|
||||
source: func(t *testing.T) afero.File {
|
||||
t.Helper()
|
||||
return openFile(t, "testdata/gray-sample.jpg")
|
||||
},
|
||||
matcher: sizeMatcher(100, 100),
|
||||
},
|
||||
"broken file": {
|
||||
options: []Option{WithMode(ResizeModeFit)},
|
||||
width: 100,
|
||||
@@ -348,6 +388,15 @@ func newGrayBmp(t *testing.T, width, height int) afero.File {
|
||||
return file
|
||||
}
|
||||
|
||||
func openFile(t *testing.T, name string) afero.File {
|
||||
appfs := afero.NewOsFs()
|
||||
file, err := appfs.Open(name)
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
return file
|
||||
}
|
||||
|
||||
func TestService_FormatFromExtension(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
ext string
|
||||
|
||||
BIN
img/testdata/20130612_142406.jpg
vendored
Normal file
BIN
img/testdata/20130612_142406.jpg
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 MiB |
BIN
img/testdata/IMG_2578.JPG
vendored
Normal file
BIN
img/testdata/IMG_2578.JPG
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 MiB |
BIN
img/testdata/gray-sample.jpg
vendored
Normal file
BIN
img/testdata/gray-sample.jpg
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
Reference in New Issue
Block a user