Metadata: Use mime type to determine file format and exif parser #391
Signed-off-by: Michael Mayer <michael@liquidbytes.net>
This commit is contained in:
parent
0023fdb1e2
commit
138dabd0c8
10 changed files with 224 additions and 58 deletions
9
go.mod
9
go.mod
|
@ -6,14 +6,15 @@ require (
|
|||
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/djherbis/times v1.2.0
|
||||
github.com/dsoprea/go-exif/v2 v2.0.0-20200717063959-46b1a0cd1772 // indirect
|
||||
github.com/dsoprea/go-exif/v3 v3.0.0-20200717063959-46b1a0cd1772
|
||||
github.com/dsoprea/go-heic-exif-extractor v0.0.0-20200520190950-3ae4ff88a0d1
|
||||
github.com/dsoprea/go-exif/v2 v2.0.0-20200717071058-9393e7afd446 // indirect
|
||||
github.com/dsoprea/go-exif/v3 v3.0.0-20200717071058-9393e7afd446
|
||||
github.com/dsoprea/go-heic-exif-extractor v0.0.0-20200717090456-b3d9dcddffd1
|
||||
github.com/dsoprea/go-iptc v0.0.0-20200610044640-bc9ca208b413 // indirect
|
||||
github.com/dsoprea/go-jpeg-image-structure v0.0.0-20200615034914-d40a386309d2
|
||||
github.com/dsoprea/go-jpeg-image-structure v0.0.0-20200717085400-dd2ba56ee6b8
|
||||
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect
|
||||
github.com/dsoprea/go-photoshop-info-format v0.0.0-20200610045659-121dd752914d // indirect
|
||||
github.com/dsoprea/go-png-image-structure v0.0.0-20200615034826-4cfc78940228
|
||||
github.com/dsoprea/go-tiff-image-structure v0.0.0-20200717073440-8ac81ec8b423
|
||||
github.com/dsoprea/go-utility v0.0.0-20200717064901-2fccff4aa15e // indirect
|
||||
github.com/dustin/go-humanize v1.0.0
|
||||
github.com/gin-gonic/gin v1.6.3
|
||||
|
|
13
go.sum
13
go.sum
|
@ -48,16 +48,25 @@ 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-20200717063959-46b1a0cd1772 h1:M49UNOTa5sLju107lAoMsm93B/fHD02vWIoskmXMBm8=
|
||||
github.com/dsoprea/go-exif/v2 v2.0.0-20200717063959-46b1a0cd1772/go.mod h1:oKrjk2kb3rAR5NbtSTLUMvMSbc+k8ZosI3MaVH47noc=
|
||||
github.com/dsoprea/go-exif/v2 v2.0.0-20200717071058-9393e7afd446 h1:ruDG+2wFz+k/mDNy8x1UqWEItWNLXpvGlLv05+TlZt4=
|
||||
github.com/dsoprea/go-exif/v2 v2.0.0-20200717071058-9393e7afd446/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-20200717063959-46b1a0cd1772 h1:l/wfrK3wEH7sYpJe+Y8ZdFJW3AmsDgPoAQq2RLgKPSQ=
|
||||
github.com/dsoprea/go-exif/v3 v3.0.0-20200717063959-46b1a0cd1772/go.mod h1:0nsO1ce0mh5czxGeLo4+OCZ/C6Eo6ZlMWsz7rH/Gxv8=
|
||||
github.com/dsoprea/go-exif/v3 v3.0.0-20200717071058-9393e7afd446 h1:96yylb+JH415u6V7ykNtnEBLaZUwS1S31TnAezcvnNE=
|
||||
github.com/dsoprea/go-exif/v3 v3.0.0-20200717071058-9393e7afd446/go.mod h1:cg5SNYKHMmzxsr9X6ZeLh/nfBRHHp5PngtEPcujONtk=
|
||||
github.com/dsoprea/go-heic-exif-extractor v0.0.0-20200520190950-3ae4ff88a0d1 h1:8Tbo+OYgg7i2G3fltmpWq1if1e752aMX7Zv/sNWWJUk=
|
||||
github.com/dsoprea/go-heic-exif-extractor v0.0.0-20200520190950-3ae4ff88a0d1/go.mod h1:UwRKreeVikXn5OarSnt4OqovcEjsIgZVuc5svj7G5w4=
|
||||
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=
|
||||
github.com/dsoprea/go-iptc v0.0.0-20200609062250-162ae6b44feb/go.mod h1:kYIdx9N9NaOyD7U6D+YtExN7QhRm+5kq7//yOsRXQtM=
|
||||
github.com/dsoprea/go-iptc v0.0.0-20200610044640-bc9ca208b413 h1:YDRiMEm32T60Kpm35YzOK9ZHgjsS1Qrid+XskNcsdp8=
|
||||
github.com/dsoprea/go-iptc v0.0.0-20200610044640-bc9ca208b413/go.mod h1:kYIdx9N9NaOyD7U6D+YtExN7QhRm+5kq7//yOsRXQtM=
|
||||
github.com/dsoprea/go-jpeg-image-structure v0.0.0-20200615034914-d40a386309d2 h1:8HmMqu64P4ZDGtcVwZDfmS4xuLXYjf2iery8teY7d9c=
|
||||
github.com/dsoprea/go-jpeg-image-structure v0.0.0-20200615034914-d40a386309d2/go.mod h1:ZoOP3yUG0HD1T4IUjIFsz/2OAB2yB4YX6NSm4K+uJRg=
|
||||
github.com/dsoprea/go-jpeg-image-structure v0.0.0-20200717085400-dd2ba56ee6b8 h1:cXCR9FOOkTEZ3t+asmy3lLv2AKYAah2igfx7WnNnVMc=
|
||||
github.com/dsoprea/go-jpeg-image-structure v0.0.0-20200717085400-dd2ba56ee6b8/go.mod h1:ZoOP3yUG0HD1T4IUjIFsz/2OAB2yB4YX6NSm4K+uJRg=
|
||||
github.com/dsoprea/go-logging v0.0.0-20190624164917-c4f10aab7696 h1:VGFnZAcLwPpt1sHlAxml+pGLZz9A2s+K/s1YNhPC91Y=
|
||||
github.com/dsoprea/go-logging v0.0.0-20190624164917-c4f10aab7696/go.mod h1:Nm/x2ZUNRW6Fe5C3LxdY1PyZY5wmDv/s5dkPJ/VB3iA=
|
||||
github.com/dsoprea/go-logging v0.0.0-20200517223158-a10564966e9d h1:F/7L5wr/fP/SKeO5HuMlNEX9Ipyx2MbH2rV9G4zJRpk=
|
||||
|
@ -70,12 +79,16 @@ github.com/dsoprea/go-photoshop-info-format v0.0.0-20200610045659-121dd752914d h
|
|||
github.com/dsoprea/go-photoshop-info-format v0.0.0-20200610045659-121dd752914d/go.mod h1:pqKB+ijp27cEcrHxhXVgUUMlSDRuGJJp1E+20Lj5H0E=
|
||||
github.com/dsoprea/go-png-image-structure v0.0.0-20200615034826-4cfc78940228 h1:GKAdOrszPH3mQ44eRg2kw9zBW0hi2L78ZNjkTx+cte0=
|
||||
github.com/dsoprea/go-png-image-structure v0.0.0-20200615034826-4cfc78940228/go.mod h1:aDYQkL/5gfRNZkoxiLTSWU4Y8/gV/4MVsy/MU9uwTak=
|
||||
github.com/dsoprea/go-tiff-image-structure v0.0.0-20200717073440-8ac81ec8b423 h1:aIXEGtyKFKqeNW2rc4cx3J2TLxQ9F5fwWPSbq6p6Fq8=
|
||||
github.com/dsoprea/go-tiff-image-structure v0.0.0-20200717073440-8ac81ec8b423/go.mod h1:we+M+yrq8ifsA33a7C7p8E1ztBbdDYjMIC8RMm8KPL8=
|
||||
github.com/dsoprea/go-utility v0.0.0-20200512094054-1abbbc781176 h1:CfXezFYb2STGOd1+n1HshvE191zVx+QX3A1nML5xxME=
|
||||
github.com/dsoprea/go-utility v0.0.0-20200512094054-1abbbc781176/go.mod h1:95+K3z2L0mqsVYd6yveIv1lmtT3tcQQ3dVakPySffW8=
|
||||
github.com/dsoprea/go-utility v0.0.0-20200711062821-fab8125e9bdf h1:/w4QxepU4AHh3AuO6/g8y/YIIHH5+aKP3Bj8sg5cqhU=
|
||||
github.com/dsoprea/go-utility v0.0.0-20200711062821-fab8125e9bdf/go.mod h1:95+K3z2L0mqsVYd6yveIv1lmtT3tcQQ3dVakPySffW8=
|
||||
github.com/dsoprea/go-utility v0.0.0-20200717064901-2fccff4aa15e h1:ojqYA1mU6LuRm8XzrVOvyfb000y59cbUcu6Wt8sFSAs=
|
||||
github.com/dsoprea/go-utility v0.0.0-20200717064901-2fccff4aa15e/go.mod h1:KVK+/Hul09ujXAGq+42UBgCTnXkiJZRnLYdURGjQUwo=
|
||||
github.com/dsoprea/go-utility/v2 v2.0.0-20200717064901-2fccff4aa15e h1:IxIbA7VbCNrwumIYjDoMOdf4KOSkMC6NJE4s8oRbE7E=
|
||||
github.com/dsoprea/go-utility/v2 v2.0.0-20200717064901-2fccff4aa15e/go.mod h1:uAzdkPTub5Y9yQwXe8W4m2XuP0tK4a9Q/dantD0+uaU=
|
||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
|
|
|
@ -3,7 +3,6 @@ package meta
|
|||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
|
@ -15,6 +14,8 @@ import (
|
|||
heicexif "github.com/dsoprea/go-heic-exif-extractor"
|
||||
"github.com/dsoprea/go-jpeg-image-structure"
|
||||
"github.com/dsoprea/go-png-image-structure"
|
||||
"github.com/dsoprea/go-tiff-image-structure"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
"gopkg.in/ugjka/go-tz.v2/tz"
|
||||
)
|
||||
|
@ -38,14 +39,14 @@ func ValidDateTime(s string) bool {
|
|||
}
|
||||
|
||||
// Exif parses an image file for Exif meta data and returns as Data struct.
|
||||
func Exif(fileName string) (data Data, err error) {
|
||||
err = data.Exif(fileName)
|
||||
func Exif(fileName string, fileType fs.FileType) (data Data, err error) {
|
||||
err = data.Exif(fileName, fileType)
|
||||
|
||||
return data, err
|
||||
}
|
||||
|
||||
// Exif parses an image file for Exif meta data and returns as Data struct.
|
||||
func (data *Data) Exif(fileName string) (err error) {
|
||||
func (data *Data) Exif(fileName string, fileType fs.FileType) (err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = fmt.Errorf("metadata: %s in %s (exif panic)\nstack: %s", e, txt.Quote(filepath.Base(fileName)), debug.Stack())
|
||||
|
@ -57,12 +58,11 @@ func (data *Data) Exif(fileName string) (err error) {
|
|||
var parsed bool
|
||||
|
||||
logName := txt.Quote(filepath.Base(fileName))
|
||||
ext := strings.ToLower(path.Ext(fileName))
|
||||
|
||||
if ext == ".jpg" || ext == ".jpeg" {
|
||||
jmp := jpegstructure.NewJpegMediaParser()
|
||||
if fileType == fs.TypeJpeg {
|
||||
jpegMp := jpegstructure.NewJpegMediaParser()
|
||||
|
||||
sl, err := jmp.ParseFile(fileName)
|
||||
sl, err := jpegMp.ParseFile(fileName)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -81,10 +81,10 @@ func (data *Data) Exif(fileName string) (err error) {
|
|||
} else {
|
||||
parsed = true
|
||||
}
|
||||
} else if ext == ".png" {
|
||||
pmp := pngstructure.NewPngMediaParser()
|
||||
} else if fileType == fs.TypePng {
|
||||
pngMp := pngstructure.NewPngMediaParser()
|
||||
|
||||
cs, err := pmp.ParseFile(fileName)
|
||||
cs, err := pngMp.ParseFile(fileName)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -101,10 +101,10 @@ func (data *Data) Exif(fileName string) (err error) {
|
|||
} else {
|
||||
parsed = true
|
||||
}
|
||||
} else if ext == ".heic" {
|
||||
hmp := heicexif.NewHeicExifMediaParser()
|
||||
} else if fileType == fs.TypeHEIF {
|
||||
heicMp := heicexif.NewHeicExifMediaParser()
|
||||
|
||||
cs, err := hmp.ParseFile(fileName)
|
||||
cs, err := heicMp.ParseFile(fileName)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -121,6 +121,26 @@ func (data *Data) Exif(fileName string) (err error) {
|
|||
} else {
|
||||
parsed = true
|
||||
}
|
||||
} else if fileType == fs.TypeTiff {
|
||||
tiffMp := tiffstructure.NewTiffMediaParser()
|
||||
|
||||
cs, err := tiffMp.ParseFile(fileName)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, rawExif, err = cs.Exif()
|
||||
|
||||
if err != nil {
|
||||
if err.Error() == "file does not have EXIF" {
|
||||
return fmt.Errorf("metadata: no exif header in %s (parse tiff)", logName)
|
||||
} else {
|
||||
log.Warnf("metadata: %s in %s (parse tiff)", err, logName)
|
||||
}
|
||||
} else {
|
||||
parsed = true
|
||||
}
|
||||
}
|
||||
|
||||
if !parsed {
|
||||
|
|
|
@ -3,12 +3,13 @@ package meta
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestExif(t *testing.T) {
|
||||
t.Run("photoshop.jpg", func(t *testing.T) {
|
||||
data, err := Exif("testdata/photoshop.jpg")
|
||||
data, err := Exif("testdata/photoshop.jpg", fs.TypeJpeg)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -37,7 +38,7 @@ func TestExif(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("ladybug.jpg", func(t *testing.T) {
|
||||
data, err := Exif("testdata/ladybug.jpg")
|
||||
data, err := Exif("testdata/ladybug.jpg", fs.TypeJpeg)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -69,7 +70,7 @@ func TestExif(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("gopro_hd2.jpg", func(t *testing.T) {
|
||||
data, err := Exif("testdata/gopro_hd2.jpg")
|
||||
data, err := Exif("testdata/gopro_hd2.jpg", fs.TypeJpeg)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -98,7 +99,7 @@ func TestExif(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("tweethog.png", func(t *testing.T) {
|
||||
_, err := Exif("testdata/tweethog.png")
|
||||
_, err := Exif("testdata/tweethog.png", fs.TypePng)
|
||||
|
||||
if err == nil {
|
||||
t.Fatal("err should NOT be nil")
|
||||
|
@ -108,7 +109,7 @@ func TestExif(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("iphone_7.heic", func(t *testing.T) {
|
||||
data, err := Exif("testdata/iphone_7.heic")
|
||||
data, err := Exif("testdata/iphone_7.heic", fs.TypeHEIF)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -129,7 +130,7 @@ func TestExif(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("gps-2000.jpg", func(t *testing.T) {
|
||||
data, err := Exif("testdata/gps-2000.jpg")
|
||||
data, err := Exif("testdata/gps-2000.jpg", fs.TypeJpeg)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -157,7 +158,7 @@ func TestExif(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("image-2011.jpg", func(t *testing.T) {
|
||||
data, err := Exif("testdata/image-2011.jpg")
|
||||
data, err := Exif("testdata/image-2011.jpg", fs.TypeJpeg)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -192,7 +193,7 @@ func TestExif(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("ship.jpg", func(t *testing.T) {
|
||||
data, err := Exif("testdata/ship.jpg")
|
||||
data, err := Exif("testdata/ship.jpg", fs.TypeJpeg)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -213,7 +214,7 @@ func TestExif(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("no-exif-data.jpg", func(t *testing.T) {
|
||||
_, err := Exif("testdata/no-exif-data.jpg")
|
||||
_, err := Exif("testdata/no-exif-data.jpg", fs.TypeJpeg)
|
||||
|
||||
if err == nil {
|
||||
t.Fatal("err should NOT be nil")
|
||||
|
@ -223,7 +224,7 @@ func TestExif(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("screenshot.png", func(t *testing.T) {
|
||||
data, err := Exif("testdata/screenshot.png")
|
||||
data, err := Exif("testdata/screenshot.png", fs.TypePng)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -234,7 +235,7 @@ func TestExif(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("orientation.jpg", func(t *testing.T) {
|
||||
data, err := Exif("testdata/orientation.jpg")
|
||||
data, err := Exif("testdata/orientation.jpg", fs.TypeJpeg)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -262,13 +263,13 @@ func TestExif(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("gopher-preview.jpg", func(t *testing.T) {
|
||||
_, err := Exif("testdata/gopher-preview.jpg")
|
||||
_, err := Exif("testdata/gopher-preview.jpg", fs.TypeJpeg)
|
||||
|
||||
assert.EqualError(t, err, "metadata: no exif header in gopher-preview.jpg (search and extract)")
|
||||
})
|
||||
|
||||
t.Run("huawei-gps-error.jpg", func(t *testing.T) {
|
||||
data, err := Exif("testdata/huawei-gps-error.jpg")
|
||||
data, err := Exif("testdata/huawei-gps-error.jpg", fs.TypeJpeg)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -289,7 +290,7 @@ func TestExif(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("panorama360.jpg", func(t *testing.T) {
|
||||
data, err := Exif("testdata/panorama360.jpg")
|
||||
data, err := Exif("testdata/panorama360.jpg", fs.TypeJpeg)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -315,7 +316,38 @@ func TestExif(t *testing.T) {
|
|||
assert.Equal(t, "", data.CameraOwner)
|
||||
assert.Equal(t, "", data.CameraSerial)
|
||||
assert.Equal(t, 6, data.FocalLength)
|
||||
assert.Equal(t, 0, int(data.Orientation))
|
||||
assert.Equal(t, 0, data.Orientation)
|
||||
assert.Equal(t, "", data.Projection)
|
||||
})
|
||||
|
||||
t.Run("exif-example.tiff", func(t *testing.T) {
|
||||
data, err := Exif("testdata/exif-example.tiff", fs.TypeTiff)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// t.Logf("all: %+v", data.All)
|
||||
|
||||
assert.Equal(t, "", data.Artist)
|
||||
assert.Equal(t, "0001-01-01T00:00:00Z", data.TakenAt.Format("2006-01-02T15:04:05Z"))
|
||||
assert.Equal(t, "0001-01-01T00:00:00Z", data.TakenAtLocal.Format("2006-01-02T15:04:05Z"))
|
||||
assert.Equal(t, "", data.Title)
|
||||
assert.Equal(t, "", data.Keywords)
|
||||
assert.Equal(t, "", data.Description)
|
||||
assert.Equal(t, "", data.Copyright)
|
||||
assert.Equal(t, 43, data.Height)
|
||||
assert.Equal(t, 65, data.Width)
|
||||
assert.Equal(t, float32(0), data.Lat)
|
||||
assert.Equal(t, float32(0), data.Lng)
|
||||
assert.Equal(t, 0, data.Altitude)
|
||||
assert.Equal(t, "", data.Exposure)
|
||||
assert.Equal(t, "", data.CameraMake)
|
||||
assert.Equal(t, "", data.CameraModel)
|
||||
assert.Equal(t, "", data.CameraOwner)
|
||||
assert.Equal(t, "", data.CameraSerial)
|
||||
assert.Equal(t, 0, data.FocalLength)
|
||||
assert.Equal(t, 1, data.Orientation)
|
||||
assert.Equal(t, "", data.Projection)
|
||||
})
|
||||
}
|
||||
|
|
BIN
internal/meta/testdata/exif-example.tiff
vendored
Normal file
BIN
internal/meta/testdata/exif-example.tiff
vendored
Normal file
Binary file not shown.
|
@ -166,6 +166,10 @@ func (ind *Index) Start(opt IndexOptions) fs.Done {
|
|||
var files MediaFiles
|
||||
|
||||
for _, f := range related.Files {
|
||||
if ind.files.Ignore(f.RelName(originalsPath), f.ModTime(), opt.Rescan) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if done[f.FileName()].Processed() {
|
||||
continue
|
||||
}
|
||||
|
@ -173,7 +177,6 @@ func (ind *Index) Start(opt IndexOptions) fs.Done {
|
|||
files = append(files, f)
|
||||
filesIndexed++
|
||||
done[f.FileName()] = fs.Processed
|
||||
ind.files.Add(f.RelName(originalsPath), f.ModTime())
|
||||
}
|
||||
|
||||
filesIndexed++
|
||||
|
|
|
@ -574,6 +574,21 @@ func (m *MediaFile) IsJpeg() bool {
|
|||
return m.MimeType() == fs.MimeTypeJpeg
|
||||
}
|
||||
|
||||
// IsPng returns true if this is a PNG file.
|
||||
func (m *MediaFile) IsPng() bool {
|
||||
return m.MimeType() == fs.MimeTypePng
|
||||
}
|
||||
|
||||
// IsGif returns true if this is a GIF file.
|
||||
func (m *MediaFile) IsGif() bool {
|
||||
return m.MimeType() == fs.MimeTypeGif
|
||||
}
|
||||
|
||||
// IsBitmap returns true if this is a bitmap file.
|
||||
func (m *MediaFile) IsBitmap() bool {
|
||||
return m.MimeType() == fs.MimeTypeBitmap
|
||||
}
|
||||
|
||||
// IsJson return true if this media file is a json sidecar file.
|
||||
func (m *MediaFile) IsJson() bool {
|
||||
return m.HasFileType(fs.TypeJson)
|
||||
|
@ -581,11 +596,18 @@ func (m *MediaFile) IsJson() bool {
|
|||
|
||||
// FileType returns the file type (jpg, gif, tiff,...).
|
||||
func (m *MediaFile) FileType() fs.FileType {
|
||||
if m.IsJpeg() {
|
||||
switch {
|
||||
case m.IsJpeg():
|
||||
return fs.TypeJpeg
|
||||
case m.IsPng():
|
||||
return fs.TypePng
|
||||
case m.IsGif():
|
||||
return fs.TypeGif
|
||||
case m.IsBitmap():
|
||||
return fs.TypeBitmap
|
||||
default:
|
||||
return fs.GetFileType(m.fileName)
|
||||
}
|
||||
|
||||
return fs.GetFileType(m.fileName)
|
||||
}
|
||||
|
||||
// MediaType returns the media type (video, image, raw, sidecar,...).
|
||||
|
@ -607,11 +629,6 @@ func (m *MediaFile) IsRaw() bool {
|
|||
return m.HasFileType(fs.TypeRaw)
|
||||
}
|
||||
|
||||
// IsPng returns true if this is a PNG file.
|
||||
func (m *MediaFile) IsPng() bool {
|
||||
return m.HasFileType(fs.TypePng)
|
||||
}
|
||||
|
||||
// IsTiff returns true if this is a TIFF file.
|
||||
func (m *MediaFile) IsTiff() bool {
|
||||
return m.HasFileType(fs.TypeTiff)
|
||||
|
@ -619,14 +636,8 @@ func (m *MediaFile) IsTiff() bool {
|
|||
|
||||
// IsImageOther returns true if this is a PNG, GIF, BMP or TIFF file.
|
||||
func (m *MediaFile) IsImageOther() bool {
|
||||
switch m.FileType() {
|
||||
case fs.TypeBitmap:
|
||||
return true
|
||||
case fs.TypeGif:
|
||||
return true
|
||||
case fs.TypePng:
|
||||
return true
|
||||
case fs.TypeTiff:
|
||||
switch {
|
||||
case m.IsPng(), m.IsGif(), m.IsTiff(), m.IsBitmap():
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
|
@ -663,6 +674,11 @@ func (m *MediaFile) IsPhoto() bool {
|
|||
return m.IsJpeg() || m.IsRaw() || m.IsHEIF() || m.IsImageOther()
|
||||
}
|
||||
|
||||
// ExifSupported returns true if parsing exif metadata is supported for the media file type.
|
||||
func (m *MediaFile) ExifSupported() bool {
|
||||
return m.IsJpeg() || m.IsRaw() || m.IsHEIF() || m.IsPng() || m.IsTiff()
|
||||
}
|
||||
|
||||
// IsMedia returns true if this is a media file (photo or video, not sidecar or other).
|
||||
func (m *MediaFile) IsMedia() bool {
|
||||
return m.IsJpeg() || m.IsVideo() || m.IsRaw() || m.IsHEIF() || m.IsImageOther()
|
||||
|
@ -707,7 +723,7 @@ func (m *MediaFile) HasJson() bool {
|
|||
|
||||
func (m *MediaFile) decodeDimensions() error {
|
||||
if !m.IsMedia() {
|
||||
return fmt.Errorf("not a photo: %s", m.FileName())
|
||||
return fmt.Errorf("failed decoding dimensions for %s", txt.Quote(m.BaseName()))
|
||||
}
|
||||
|
||||
var width, height int
|
||||
|
@ -719,7 +735,7 @@ func (m *MediaFile) decodeDimensions() error {
|
|||
height = data.Height
|
||||
}
|
||||
|
||||
if m.IsJpeg() {
|
||||
if m.IsJpeg() || m.IsPng() || m.IsGif() {
|
||||
file, err := os.Open(m.FileName())
|
||||
|
||||
if err != nil || file == nil {
|
||||
|
|
|
@ -1098,9 +1098,13 @@ func TestMediaFile_IsPng(t *testing.T) {
|
|||
conf := config.TestConfig()
|
||||
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/tweethog.png")
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, fs.TypePng, mediaFile.FileType())
|
||||
assert.Equal(t, "image/png", mediaFile.MimeType())
|
||||
assert.Equal(t, true, mediaFile.IsPng())
|
||||
})
|
||||
}
|
||||
|
@ -1113,6 +1117,8 @@ func TestMediaFile_IsTiff(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, fs.TypeJson, mediaFile.FileType())
|
||||
assert.Equal(t, "text/plain; charset=utf-8", mediaFile.MimeType())
|
||||
assert.Equal(t, false, mediaFile.IsTiff())
|
||||
})
|
||||
t.Run("/purple.tiff", func(t *testing.T) {
|
||||
|
@ -1122,6 +1128,19 @@ func TestMediaFile_IsTiff(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, fs.TypeTiff, mediaFile.FileType())
|
||||
assert.Equal(t, "application/octet-stream", mediaFile.MimeType())
|
||||
assert.Equal(t, true, mediaFile.IsTiff())
|
||||
})
|
||||
t.Run("/example.tiff", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/example.tif")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, fs.TypeTiff, mediaFile.FileType())
|
||||
assert.Equal(t, "application/octet-stream", mediaFile.MimeType())
|
||||
assert.Equal(t, true, mediaFile.IsTiff())
|
||||
})
|
||||
}
|
||||
|
@ -1161,6 +1180,9 @@ func TestMediaFile_IsImageOther(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, fs.TypeBitmap, mediaFile.FileType())
|
||||
assert.Equal(t, "image/bmp", mediaFile.MimeType())
|
||||
assert.Equal(t, true, mediaFile.IsBitmap())
|
||||
assert.Equal(t, true, mediaFile.IsImageOther())
|
||||
})
|
||||
t.Run("/preloader.gif", func(t *testing.T) {
|
||||
|
@ -1170,6 +1192,9 @@ func TestMediaFile_IsImageOther(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, fs.TypeGif, mediaFile.FileType())
|
||||
assert.Equal(t, "image/gif", mediaFile.MimeType())
|
||||
assert.Equal(t, true, mediaFile.IsImageOther())
|
||||
})
|
||||
}
|
||||
|
@ -1409,8 +1434,9 @@ func TestMediaFile_decodeDimension(t *testing.T) {
|
|||
|
||||
decodeErr := mediaFile.decodeDimensions()
|
||||
|
||||
assert.EqualError(t, decodeErr, "not a photo: "+conf.ExamplesPath()+"/Random.docx")
|
||||
assert.EqualError(t, decodeErr, "failed decoding dimensions for Random.docx")
|
||||
})
|
||||
|
||||
t.Run("clock_purple.jpg", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
|
||||
|
@ -1424,6 +1450,7 @@ func TestMediaFile_decodeDimension(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("iphone_7.heic", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
|
||||
|
@ -1437,6 +1464,57 @@ func TestMediaFile_decodeDimension(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("example.png", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/example.png")
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := mediaFile.decodeDimensions(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, 100, mediaFile.Width())
|
||||
assert.Equal(t, 67, mediaFile.Height())
|
||||
})
|
||||
|
||||
t.Run("example.gif", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/example.gif")
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := mediaFile.decodeDimensions(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, 100, mediaFile.Width())
|
||||
assert.Equal(t, 67, mediaFile.Height())
|
||||
})
|
||||
|
||||
t.Run("blue-go-video.mp4", func(t *testing.T) {
|
||||
conf := config.TestConfig()
|
||||
|
||||
mediaFile, err := NewMediaFile(conf.ExamplesPath() + "/blue-go-video.mp4")
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := mediaFile.decodeDimensions(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, 1920, mediaFile.Width())
|
||||
assert.Equal(t, 1080, mediaFile.Height())
|
||||
})
|
||||
}
|
||||
|
||||
func TestMediaFile_Width(t *testing.T) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package photoprism
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/meta"
|
||||
|
@ -14,10 +14,10 @@ func (m *MediaFile) MetaData() (result meta.Data) {
|
|||
m.metaDataOnce.Do(func() {
|
||||
var err error
|
||||
|
||||
if m.IsPhoto() {
|
||||
err = m.metaData.Exif(m.FileName())
|
||||
if m.ExifSupported() {
|
||||
err = m.metaData.Exif(m.FileName(), m.FileType())
|
||||
} else {
|
||||
err = errors.New("not a photo")
|
||||
err = fmt.Errorf("exif not supported: %s", txt.Quote(m.BaseName()))
|
||||
}
|
||||
|
||||
// Parse JSON sidecar file names as Google Photos uses them ("img_1234.jpg.json").
|
||||
|
|
|
@ -6,7 +6,10 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
MimeTypeJpeg = "image/jpeg"
|
||||
MimeTypeJpeg = "image/jpeg"
|
||||
MimeTypePng = "image/png"
|
||||
MimeTypeGif = "image/gif"
|
||||
MimeTypeBitmap = "image/bmp"
|
||||
)
|
||||
|
||||
// MimeType returns the mime type of a file, empty string if unknown.
|
||||
|
|
Loading…
Reference in a new issue