Exif: Refactor JPEG rotation based on orientation flag #1064
We now manually detect and change the rotation, the imaging autorotation functionality was disabled for our core use-cases. anymore.
This commit is contained in:
parent
1d108199ef
commit
01d4b1ee31
13 changed files with 99 additions and 52 deletions
6
go.sum
6
go.sum
|
@ -49,13 +49,9 @@ github.com/dsoprea/go-exif/v2 v2.0.0-20200520183328-015129a9efd5/go.mod h1:9EXlP
|
|||
github.com/dsoprea/go-exif/v2 v2.0.0-20200604193436-ca8584a0e1c4/go.mod h1:9EXlPeHfblFFnwu5UOqmP2eoZfJyAZ2Ri/Vki33ajO0=
|
||||
github.com/dsoprea/go-exif/v2 v2.0.0-20200807075213-089aa48c91e6 h1:jkmCBceHmez4ArDFqcIrjFhPTTIV2IlWiF/QTeubgOs=
|
||||
github.com/dsoprea/go-exif/v2 v2.0.0-20200807075213-089aa48c91e6/go.mod h1:oKrjk2kb3rAR5NbtSTLUMvMSbc+k8ZosI3MaVH47noc=
|
||||
github.com/dsoprea/go-exif/v2 v2.0.0-20210131231135-d154f10435cc h1:F8AmoUFkSqzbZoGrIGWpQqbh3qosJl3h8zdVusOOggQ=
|
||||
github.com/dsoprea/go-exif/v2 v2.0.0-20210131231135-d154f10435cc/go.mod h1:oKrjk2kb3rAR5NbtSTLUMvMSbc+k8ZosI3MaVH47noc=
|
||||
github.com/dsoprea/go-exif/v3 v3.0.0-20200717053412-08f1b6708903/go.mod h1:0nsO1ce0mh5czxGeLo4+OCZ/C6Eo6ZlMWsz7rH/Gxv8=
|
||||
github.com/dsoprea/go-exif/v3 v3.0.0-20200807075213-089aa48c91e6 h1:AWLaaemM6TvO4DVwMtXibJKpWWfyw+tiZwYUiueLPzE=
|
||||
github.com/dsoprea/go-exif/v3 v3.0.0-20200807075213-089aa48c91e6/go.mod h1:cg5SNYKHMmzxsr9X6ZeLh/nfBRHHp5PngtEPcujONtk=
|
||||
github.com/dsoprea/go-exif/v3 v3.0.0-20210131231135-d154f10435cc h1:WlJC9DefVe1OZKM04jD7jInkZ9Oyou+K6cpYOVPXq0o=
|
||||
github.com/dsoprea/go-exif/v3 v3.0.0-20210131231135-d154f10435cc/go.mod h1:cg5SNYKHMmzxsr9X6ZeLh/nfBRHHp5PngtEPcujONtk=
|
||||
github.com/dsoprea/go-heic-exif-extractor v0.0.0-20200717090456-b3d9dcddffd1 h1:R/EEzpxqQxeEcJ/z0EFTI1U6XsuOnepyp5o1uZg5c2E=
|
||||
github.com/dsoprea/go-heic-exif-extractor v0.0.0-20200717090456-b3d9dcddffd1/go.mod h1:UwRKreeVikXn5OarSnt4OqovcEjsIgZVuc5svj7G5w4=
|
||||
github.com/dsoprea/go-iptc v0.0.0-20200609062250-162ae6b44feb h1:gwjJjUr6FY7zAWVEueFPrcRHhd9+IK81TcItbqw2du4=
|
||||
|
@ -126,8 +122,6 @@ github.com/golang/geo v0.0.0-20190916061304-5b978397cfec h1:lJwO/92dFXWeXOZdoGXg
|
|||
github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||
github.com/golang/geo v0.0.0-20200319012246-673a6f80352d h1:C/hKUcHT483btRbeGkrRjJz+Zbcj8audldIi9tRJDCc=
|
||||
github.com/golang/geo v0.0.0-20200319012246-673a6f80352d/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||
github.com/golang/geo v0.0.0-20210108004804-a63082ebfb66 h1:wNA26/2ftrz6nI4dbIim6OSKtLlNdjpNiwFB+l/yqtQ=
|
||||
github.com/golang/geo v0.0.0-20210108004804-a63082ebfb66/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo=
|
||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
|
|
|
@ -102,7 +102,7 @@ func AlbumCover(router *gin.RouterGroup) {
|
|||
var thumbnail string
|
||||
|
||||
if conf.ThumbUncached() || thumbType.OnDemand() {
|
||||
thumbnail, err = thumb.FromFile(fileName, f.FileHash, conf.ThumbPath(), thumbType.Width, thumbType.Height, thumbType.Options...)
|
||||
thumbnail, err = thumb.FromFile(fileName, f.FileHash, conf.ThumbPath(), thumbType.Width, thumbType.Height, f.FileOrientation, thumbType.Options...)
|
||||
} else {
|
||||
thumbnail, err = thumb.FromCache(fileName, f.FileHash, conf.ThumbPath(), thumbType.Width, thumbType.Height, thumbType.Options...)
|
||||
}
|
||||
|
@ -214,7 +214,7 @@ func LabelCover(router *gin.RouterGroup) {
|
|||
var thumbnail string
|
||||
|
||||
if conf.ThumbUncached() || thumbType.OnDemand() {
|
||||
thumbnail, err = thumb.FromFile(fileName, f.FileHash, conf.ThumbPath(), thumbType.Width, thumbType.Height, thumbType.Options...)
|
||||
thumbnail, err = thumb.FromFile(fileName, f.FileHash, conf.ThumbPath(), thumbType.Width, thumbType.Height, f.FileOrientation, thumbType.Options...)
|
||||
} else {
|
||||
thumbnail, err = thumb.FromCache(fileName, f.FileHash, conf.ThumbPath(), thumbType.Width, thumbType.Height, thumbType.Options...)
|
||||
}
|
||||
|
|
|
@ -111,7 +111,7 @@ func GetFolderCover(router *gin.RouterGroup) {
|
|||
var thumbnail string
|
||||
|
||||
if conf.ThumbUncached() || thumbType.OnDemand() {
|
||||
thumbnail, err = thumb.FromFile(fileName, f.FileHash, conf.ThumbPath(), thumbType.Width, thumbType.Height, thumbType.Options...)
|
||||
thumbnail, err = thumb.FromFile(fileName, f.FileHash, conf.ThumbPath(), thumbType.Width, thumbType.Height, f.FileOrientation, thumbType.Options...)
|
||||
} else {
|
||||
thumbnail, err = thumb.FromCache(fileName, f.FileHash, conf.ThumbPath(), thumbType.Width, thumbType.Height, thumbType.Options...)
|
||||
}
|
||||
|
|
|
@ -139,7 +139,7 @@ func GetThumb(router *gin.RouterGroup) {
|
|||
var thumbnail string
|
||||
|
||||
if conf.ThumbUncached() || thumbType.OnDemand() {
|
||||
thumbnail, err = thumb.FromFile(fileName, f.FileHash, conf.ThumbPath(), thumbType.Width, thumbType.Height, thumbType.Options...)
|
||||
thumbnail, err = thumb.FromFile(fileName, f.FileHash, conf.ThumbPath(), thumbType.Width, thumbType.Height, f.FileOrientation, thumbType.Options...)
|
||||
} else {
|
||||
thumbnail, err = thumb.FromCache(fileName, f.FileHash, conf.ThumbPath(), thumbType.Width, thumbType.Height, thumbType.Options...)
|
||||
}
|
||||
|
|
|
@ -98,7 +98,7 @@ func SharePreview(router *gin.RouterGroup) {
|
|||
return
|
||||
}
|
||||
|
||||
thumbnail, err := thumb.FromFile(fileName, f.FileHash, conf.ThumbPath(), thumbType.Width, thumbType.Height, thumbType.Options...)
|
||||
thumbnail, err := thumb.FromFile(fileName, f.FileHash, conf.ThumbPath(), thumbType.Width, thumbType.Height, f.FileOrientation, thumbType.Options...)
|
||||
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
|
@ -128,7 +128,7 @@ func SharePreview(router *gin.RouterGroup) {
|
|||
return
|
||||
}
|
||||
|
||||
thumbnail, err := thumb.FromFile(fileName, f.FileHash, conf.ThumbPath(), thumbType.Width, thumbType.Height, thumbType.Options...)
|
||||
thumbnail, err := thumb.FromFile(fileName, f.FileHash, conf.ThumbPath(), thumbType.Width, thumbType.Height, f.FileOrientation, thumbType.Options...)
|
||||
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
|
|
|
@ -118,6 +118,14 @@ func ImportWorker(jobs <-chan ImportJob) {
|
|||
continue
|
||||
}
|
||||
|
||||
if f.NeedsExifToolJson() {
|
||||
if jsonName, err := imp.convert.ToJson(f); err != nil {
|
||||
log.Debugf("import: %s in %s (extract metadata)", txt.Quote(err.Error()), txt.Quote(f.BaseName()))
|
||||
} else {
|
||||
log.Debugf("import: %s created", filepath.Base(jsonName))
|
||||
}
|
||||
}
|
||||
|
||||
if f.IsMedia() && !f.HasJpeg() {
|
||||
if jpegFile, err := imp.convert.ToJpeg(f); err != nil {
|
||||
log.Errorf("import: %s in %s (convert to jpeg)", err.Error(), txt.Quote(fs.RelName(destMainFileName, imp.originalsPath())))
|
||||
|
@ -136,14 +144,6 @@ func ImportWorker(jobs <-chan ImportJob) {
|
|||
}
|
||||
}
|
||||
|
||||
if f.NeedsExifToolJson() {
|
||||
if jsonName, err := imp.convert.ToJson(f); err != nil {
|
||||
log.Debugf("import: %s in %s (extract metadata)", txt.Quote(err.Error()), txt.Quote(f.BaseName()))
|
||||
} else {
|
||||
log.Debugf("import: %s created", filepath.Base(jsonName))
|
||||
}
|
||||
}
|
||||
|
||||
related, err := f.RelatedFiles(imp.conf.Settings().StackSequences())
|
||||
|
||||
if err != nil {
|
||||
|
|
|
@ -27,6 +27,14 @@ func IndexMain(related *RelatedFiles, ind *Index, opt IndexOptions) (result Inde
|
|||
return result
|
||||
}
|
||||
|
||||
if f.NeedsExifToolJson() {
|
||||
if jsonName, err := ind.convert.ToJson(f); err != nil {
|
||||
log.Debugf("index: %s in %s (extract metadata)", txt.Quote(err.Error()), txt.Quote(f.BaseName()))
|
||||
} else {
|
||||
log.Debugf("index: %s created", filepath.Base(jsonName))
|
||||
}
|
||||
}
|
||||
|
||||
if opt.Convert && f.IsMedia() && !f.HasJpeg() {
|
||||
if jpegFile, err := ind.convert.ToJpeg(f); err != nil {
|
||||
result.Err = fmt.Errorf("index: failed converting %s to jpeg (%s)", txt.Quote(f.BaseName()), err.Error())
|
||||
|
@ -47,14 +55,6 @@ func IndexMain(related *RelatedFiles, ind *Index, opt IndexOptions) (result Inde
|
|||
}
|
||||
}
|
||||
|
||||
if f.NeedsExifToolJson() {
|
||||
if jsonName, err := ind.convert.ToJson(f); err != nil {
|
||||
log.Debugf("index: %s in %s (extract metadata)", txt.Quote(err.Error()), txt.Quote(f.BaseName()))
|
||||
} else {
|
||||
log.Debugf("index: %s created", filepath.Base(jsonName))
|
||||
}
|
||||
}
|
||||
|
||||
result = ind.MediaFile(f, opt, "")
|
||||
|
||||
if result.Indexed() && f.IsJpeg() {
|
||||
|
@ -108,6 +108,14 @@ func IndexRelated(related RelatedFiles, ind *Index, opt IndexOptions) (result In
|
|||
continue
|
||||
}
|
||||
|
||||
if f.NeedsExifToolJson() {
|
||||
if jsonName, err := ind.convert.ToJson(f); err != nil {
|
||||
log.Debugf("index: %s in %s (extract metadata)", txt.Quote(err.Error()), txt.Quote(f.BaseName()))
|
||||
} else {
|
||||
log.Debugf("index: %s created", filepath.Base(jsonName))
|
||||
}
|
||||
}
|
||||
|
||||
if opt.Convert && f.IsMedia() && !f.HasJpeg() {
|
||||
if jpegFile, err := ind.convert.ToJpeg(f); err != nil {
|
||||
result.Err = fmt.Errorf("index: failed converting %s to jpeg (%s)", txt.Quote(f.BaseName()), err.Error())
|
||||
|
@ -128,14 +136,6 @@ func IndexRelated(related RelatedFiles, ind *Index, opt IndexOptions) (result In
|
|||
}
|
||||
}
|
||||
|
||||
if f.NeedsExifToolJson() {
|
||||
if jsonName, err := ind.convert.ToJson(f); err != nil {
|
||||
log.Debugf("index: %s in %s (extract metadata)", txt.Quote(err.Error()), txt.Quote(f.BaseName()))
|
||||
} else {
|
||||
log.Debugf("index: %s created", filepath.Base(jsonName))
|
||||
}
|
||||
}
|
||||
|
||||
res := ind.MediaFile(f, opt, "")
|
||||
|
||||
if res.Indexed() && f.IsJpeg() {
|
||||
|
|
|
@ -877,7 +877,7 @@ func (m *MediaFile) Megapixels() int {
|
|||
return int(math.Round(float64(m.Width()*m.Height()) / 1000000))
|
||||
}
|
||||
|
||||
// Orientation returns the orientation of a MediaFile.
|
||||
// Orientation returns the Exif orientation of the media file.
|
||||
func (m *MediaFile) Orientation() int {
|
||||
if data := m.MetaData(); data.Error == nil {
|
||||
return data.Orientation
|
||||
|
@ -895,7 +895,7 @@ func (m *MediaFile) Thumbnail(path string, typeName string) (filename string, er
|
|||
return "", fmt.Errorf("media: invalid type %s", typeName)
|
||||
}
|
||||
|
||||
thumbnail, err := thumb.FromFile(m.FileName(), m.Hash(), path, thumbType.Width, thumbType.Height, thumbType.Options...)
|
||||
thumbnail, err := thumb.FromFile(m.FileName(), m.Hash(), path, thumbType.Width, thumbType.Height, m.Orientation(), thumbType.Options...)
|
||||
|
||||
if err != nil {
|
||||
err = fmt.Errorf("media: failed creating thumbnail for %s (%s)", txt.Quote(m.BaseName()), err)
|
||||
|
@ -914,7 +914,7 @@ func (m *MediaFile) Resample(path string, typeName string) (img image.Image, err
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return imaging.Open(filename, imaging.AutoOrientation(true))
|
||||
return imaging.Open(filename)
|
||||
}
|
||||
|
||||
// ResampleDefault pre-renders default thumbnails.
|
||||
|
@ -957,13 +957,15 @@ func (m *MediaFile) ResampleDefault(thumbPath string, force bool) (err error) {
|
|||
}
|
||||
|
||||
if originalImg == nil {
|
||||
img, err := imaging.Open(m.FileName(), imaging.AutoOrientation(true))
|
||||
img, err := imaging.Open(m.FileName())
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("media: %s in %s", err.Error(), txt.Quote(m.BaseName()))
|
||||
return err
|
||||
}
|
||||
|
||||
img = thumb.Rotate(img, m.Orientation())
|
||||
|
||||
originalImg = img
|
||||
}
|
||||
|
||||
|
|
|
@ -113,23 +113,23 @@ func TestThumb_FromFile(t *testing.T) {
|
|||
}
|
||||
|
||||
t.Run("valid parameter", func(t *testing.T) {
|
||||
fileModel := &entity.File{
|
||||
file := &entity.File{
|
||||
FileName: conf.ExamplesPath() + "/elephants.jpg",
|
||||
FileHash: "1234568889",
|
||||
}
|
||||
|
||||
thumbnail, err := thumb.FromFile(fileModel.FileName, fileModel.FileHash, thumbsPath, 224, 224)
|
||||
thumbnail, err := thumb.FromFile(file.FileName, file.FileHash, thumbsPath, 224, 224, file.FileOrientation)
|
||||
assert.Nil(t, err)
|
||||
assert.FileExists(t, thumbnail)
|
||||
})
|
||||
|
||||
t.Run("hash too short", func(t *testing.T) {
|
||||
fileModel := &entity.File{
|
||||
file := &entity.File{
|
||||
FileName: conf.ExamplesPath() + "/elephants.jpg",
|
||||
FileHash: "123",
|
||||
}
|
||||
|
||||
_, err := thumb.FromFile(fileModel.FileName, fileModel.FileHash, thumbsPath, 224, 224)
|
||||
_, err := thumb.FromFile(file.FileName, file.FileHash, thumbsPath, 224, 224, file.FileOrientation)
|
||||
|
||||
if err == nil {
|
||||
t.Fatal("err should NOT be nil")
|
||||
|
@ -138,12 +138,12 @@ func TestThumb_FromFile(t *testing.T) {
|
|||
assert.Equal(t, "resample: file hash is empty or too short (123)", err.Error())
|
||||
})
|
||||
t.Run("filename too short", func(t *testing.T) {
|
||||
fileModel := &entity.File{
|
||||
file := &entity.File{
|
||||
FileName: "xxx",
|
||||
FileHash: "12367890",
|
||||
}
|
||||
|
||||
_, err := thumb.FromFile(fileModel.FileName, fileModel.FileHash, thumbsPath, 224, 224)
|
||||
_, err := thumb.FromFile(file.FileName, file.FileHash, thumbsPath, 224, 224, file.FileOrientation)
|
||||
if err == nil {
|
||||
t.FailNow()
|
||||
}
|
||||
|
|
|
@ -124,7 +124,7 @@ func FromCache(imageFilename, hash, thumbPath string, width, height int, opts ..
|
|||
return "", ErrThumbNotCached
|
||||
}
|
||||
|
||||
func FromFile(imageFilename, hash, thumbPath string, width, height int, opts ...ResampleOption) (fileName string, err error) {
|
||||
func FromFile(imageFilename, hash, thumbPath string, width, height, orientation int, opts ...ResampleOption) (fileName string, err error) {
|
||||
if fileName, err := FromCache(imageFilename, hash, thumbPath, width, height, opts...); err == nil {
|
||||
return fileName, err
|
||||
} else if err != ErrThumbNotCached {
|
||||
|
@ -138,13 +138,17 @@ func FromFile(imageFilename, hash, thumbPath string, width, height int, opts ...
|
|||
return "", err
|
||||
}
|
||||
|
||||
img, err := imaging.Open(imageFilename, imaging.AutoOrientation(true))
|
||||
img, err := imaging.Open(imageFilename)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("resample: %s in %s", err, txt.Quote(filepath.Base(imageFilename)))
|
||||
return "", err
|
||||
}
|
||||
|
||||
if orientation > 1 {
|
||||
img = Rotate(img, orientation)
|
||||
}
|
||||
|
||||
if _, err := Create(img, fileName, width, height, opts...); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
|
@ -222,7 +222,7 @@ func TestFromFile(t *testing.T) {
|
|||
|
||||
assert.FileExists(t, src)
|
||||
|
||||
fileName, err := FromFile(src, "123456789098765432", "testdata", colorThumb.Width, colorThumb.Height, colorThumb.Options...)
|
||||
fileName, err := FromFile(src, "123456789098765432", "testdata", colorThumb.Width, colorThumb.Height, OrientationNormal, colorThumb.Options...)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -239,7 +239,7 @@ func TestFromFile(t *testing.T) {
|
|||
|
||||
assert.NoFileExists(t, src)
|
||||
|
||||
fileName, err := FromFile(src, "193456789098765432", "testdata", colorThumb.Width, colorThumb.Height, colorThumb.Options...)
|
||||
fileName, err := FromFile(src, "193456789098765432", "testdata", colorThumb.Width, colorThumb.Height, OrientationNormal, colorThumb.Options...)
|
||||
|
||||
assert.Equal(t, "", fileName)
|
||||
assert.Error(t, err)
|
||||
|
@ -247,7 +247,7 @@ func TestFromFile(t *testing.T) {
|
|||
t.Run("empty filename", func(t *testing.T) {
|
||||
colorThumb := Types["colors"]
|
||||
|
||||
fileName, err := FromFile("", "193456789098765432", "testdata", colorThumb.Width, colorThumb.Height, colorThumb.Options...)
|
||||
fileName, err := FromFile("", "193456789098765432", "testdata", colorThumb.Width, colorThumb.Height, OrientationNormal, colorThumb.Options...)
|
||||
|
||||
if err == nil {
|
||||
t.Fatal("error expected")
|
||||
|
|
47
internal/thumb/rotate.go
Normal file
47
internal/thumb/rotate.go
Normal file
|
@ -0,0 +1,47 @@
|
|||
package thumb
|
||||
|
||||
import (
|
||||
"image"
|
||||
|
||||
"github.com/disintegration/imaging"
|
||||
)
|
||||
|
||||
const (
|
||||
OrientationUnspecified int = 0
|
||||
OrientationNormal = 1
|
||||
OrientationFlipH = 2
|
||||
OrientationRotate180 = 3
|
||||
OrientationFlipV = 4
|
||||
OrientationTranspose = 5
|
||||
OrientationRotate270 = 6
|
||||
OrientationTransverse = 7
|
||||
OrientationRotate90 = 8
|
||||
)
|
||||
|
||||
// Rotate rotates an image based on the Exif orientation.
|
||||
func Rotate(img image.Image, o int) image.Image {
|
||||
switch o {
|
||||
case OrientationUnspecified:
|
||||
// Do nothing.
|
||||
case OrientationNormal:
|
||||
// Do nothing.
|
||||
case OrientationFlipH:
|
||||
img = imaging.FlipH(img)
|
||||
case OrientationFlipV:
|
||||
img = imaging.FlipV(img)
|
||||
case OrientationRotate90:
|
||||
img = imaging.Rotate90(img)
|
||||
case OrientationRotate180:
|
||||
img = imaging.Rotate180(img)
|
||||
case OrientationRotate270:
|
||||
img = imaging.Rotate270(img)
|
||||
case OrientationTranspose:
|
||||
img = imaging.Transpose(img)
|
||||
case OrientationTransverse:
|
||||
img = imaging.Transverse(img)
|
||||
default:
|
||||
log.Debugf("rotate: invalid orientation %d", o)
|
||||
}
|
||||
|
||||
return img
|
||||
}
|
|
@ -104,7 +104,7 @@ func (worker *Share) Start() (err error) {
|
|||
continue
|
||||
}
|
||||
|
||||
srcFileName, err = thumb.FromFile(srcFileName, file.File.FileHash, worker.conf.ThumbPath(), thumbType.Width, thumbType.Height, thumbType.Options...)
|
||||
srcFileName, err = thumb.FromFile(srcFileName, file.File.FileHash, worker.conf.ThumbPath(), thumbType.Width, thumbType.Height, file.File.FileOrientation, thumbType.Options...)
|
||||
|
||||
if err != nil {
|
||||
worker.logError(err)
|
||||
|
|
Loading…
Reference in a new issue