Skip to content
Permalink
Browse files

Add image.Exif

Note that we will probably need to add some metadata cache for this to scale.

Fixes #4600
  • Loading branch information...
bep committed Aug 29, 2019
1 parent 8a8d4a6 commit 28143397d625cce1f89f4161cba97c0dddd9004c
1 go.mod
@@ -41,6 +41,7 @@ require (
github.com/pkg/errors v0.8.1
github.com/rogpeppe/go-internal v1.3.0
github.com/russross/blackfriday v1.5.3-0.20190124082335-a477dd164691
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd
github.com/sanity-io/litter v1.1.0
github.com/spf13/afero v1.2.2
github.com/spf13/cast v1.3.0
2 go.sum
@@ -263,6 +263,8 @@ github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNue
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday v1.5.3-0.20190124082335-a477dd164691 h1:auJkuUc4uOuZNoH9jGLvqVaDLiuCOh/LY+Qw5NBFo4I=
github.com/russross/blackfriday v1.5.3-0.20190124082335-a477dd164691/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK3dcGsnCnO41eRBOnY12zwkn5qVwgc=
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
github.com/sanity-io/litter v1.1.0 h1:BllcKWa3VbZmOZbDCoszYLk7zCsKHz5Beossi8SUcTc=
github.com/sanity-io/litter v1.1.0/go.mod h1:CJ0VCw2q4qKU7LaQr3n7UOSHzgEMgcGco7N/SkZQPjw=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
@@ -21,6 +21,9 @@ import (
_ "image/png"
"os"
"strings"
"sync"

"github.com/gohugoio/hugo/resources/images/exif"

"github.com/gohugoio/hugo/resources/internal"

@@ -48,12 +51,56 @@ var (
type imageResource struct {
*images.Image

// When a image is processed in a chain, this holds the reference to the
// original (first).
root *imageResource

exifInit sync.Once
exifInitErr error
exif *exif.Exif

baseResource
}

// ImageData contains image related data, typically Exif.
type ImageData map[string]interface{}

func (i *imageResource) Exif() (*exif.Exif, error) {
return i.root.getExif()
}

func (i *imageResource) getExif() (*exif.Exif, error) {

i.exifInit.Do(func() {
supportsExif := i.Format == images.JPEG || i.Format == images.TIFF
if !supportsExif {
return
}

f, err := i.root.ReadSeekCloser()
if err != nil {
i.exifInitErr = err
return
}
defer f.Close()

x, err := i.getSpec().imaging.DecodeExif(f)
if err != nil {
i.exifInitErr = err
return
}

i.exif = x

})

return i.exif, i.exifInitErr
}

func (i *imageResource) Clone() resource.Resource {
gr := i.baseResource.Clone().(baseResource)
return &imageResource{
root: i.root,
Image: i.WithSpec(gr),
baseResource: gr,
}
@@ -74,6 +121,7 @@ func (i *imageResource) cloneWithUpdates(u *transformationUpdate) (baseResource,
}

return &imageResource{
root: i.root,
Image: img,
baseResource: base,
}, nil
@@ -217,6 +265,7 @@ func (i *imageResource) clone(img image.Image) *imageResource {

return &imageResource{
Image: image,
root: i.root,
baseResource: spec,
}
}
@@ -332,6 +332,32 @@ func TestSVGImageContent(t *testing.T) {
c.Assert(content.(string), qt.Contains, `<svg height="100" width="100">`)
}

func TestImageExif(t *testing.T) {
c := qt.New(t)
image := fetchImage(c, "sunset.jpg")

x, err := image.Exif()
c.Assert(err, qt.IsNil)
c.Assert(x, qt.Not(qt.IsNil))

c.Assert(x.Date.Format("2006-01-02"), qt.Equals, "2017-10-27")

// Malaga: https://goo.gl/taazZy
c.Assert(x.Lat, qt.Equals, float64(36.59744166666667))
c.Assert(x.Long, qt.Equals, float64(-4.50846))

v, found := x.Values["LensModel"]
c.Assert(found, qt.Equals, true)
lensModel, ok := v.(string)
c.Assert(ok, qt.Equals, true)
c.Assert(lensModel, qt.Equals, "smc PENTAX-DA* 16-50mm F2.8 ED AL [IF] SDM")

resized, _ := image.Resize("300x200")
x2, _ := resized.Exif()
c.Assert(x2, qt.Equals, x)

}

func TestImageOperationsGolden(t *testing.T) {
c := qt.New(t)
c.Parallel()
@@ -119,6 +119,11 @@ func DecodeConfig(m map[string]interface{}) (Imaging, error) {
i.ResampleFilter = filter
}

if strings.TrimSpace(i.Exif.IncludeFields) == "" && strings.TrimSpace(i.Exif.ExcludeFields) == "" {
// Don't change this for no good reason. Please don't.
i.Exif.ExcludeFields = "GPS|Exif|Exposure[M|P|B]|Contrast|Resolution|Sharp|JPEG|Metering|Sensing|Saturation|ColorSpace|Flash|WhiteBalance"
}

return i, nil
}

@@ -279,4 +284,29 @@ type Imaging struct {

// The anchor to use in Fill. Default is "smart", i.e. Smart Crop.
Anchor string

Exif ExifConfig
}

type ExifConfig struct {

// Regexp matching the Exif fields you want from the (massive) set of Exif info
// available. As we cache this info to disk, this is for performance and
// disk space reasons more than anything.
// If you want it all, put ".*" in this config setting.
// Note that if neither this or ExcludeFields is set, Hugo will return a small
// default set.
IncludeFields string

// Regexp matching the Exif fields you want to exclude. This may be easier to use
// than IncludeFields above, depending on what you want.
ExcludeFields string

// Hugo extracts the "photo taken" date/time into .Date by default.
// Set this to true to turn it off.
DisableDate bool

// Hugo extracts the "photo taken where" (GPS latitude and longitude) into
// .Long and .Lat. Set this to true to turn it off.
DisableLatLong bool
}
@@ -64,6 +64,16 @@ func TestDecodeConfig(t *testing.T) {
})
c.Assert(err, qt.IsNil)
c.Assert(imaging.Anchor, qt.Equals, "smart")

imaging, err = DecodeConfig(map[string]interface{}{
"exif": map[string]interface{}{
"disableLatLong": true,
},
})
c.Assert(err, qt.IsNil)
c.Assert(imaging.Exif.DisableLatLong, qt.Equals, true)
c.Assert(imaging.Exif.ExcludeFields, qt.Equals, "GPS|Exif|Exposure[M|P|B]|Contrast|Resolution|Sharp|JPEG|Metering|Sensing|Saturation|ColorSpace|Flash|WhiteBalance")

}

func TestDecodeImageConfig(t *testing.T) {

0 comments on commit 2814339

Please sign in to comment.
You can’t perform that action at this time.