mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-21 20:46:30 -05:00
parent
a28bed0817
commit
72ff937e11
12 changed files with 297 additions and 194 deletions
2
go.mod
2
go.mod
|
@ -15,6 +15,7 @@ require (
|
||||||
github.com/bep/golibsass v1.1.1
|
github.com/bep/golibsass v1.1.1
|
||||||
github.com/bep/gowebp v0.3.0
|
github.com/bep/gowebp v0.3.0
|
||||||
github.com/bep/helpers v0.4.0
|
github.com/bep/helpers v0.4.0
|
||||||
|
github.com/bep/imagemeta v0.7.0
|
||||||
github.com/bep/lazycache v0.4.0
|
github.com/bep/lazycache v0.4.0
|
||||||
github.com/bep/logg v0.4.0
|
github.com/bep/logg v0.4.0
|
||||||
github.com/bep/mclib v1.20400.20402
|
github.com/bep/mclib v1.20400.20402
|
||||||
|
@ -60,7 +61,6 @@ require (
|
||||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58
|
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2
|
github.com/pelletier/go-toml/v2 v2.2.2
|
||||||
github.com/rogpeppe/go-internal v1.12.0
|
github.com/rogpeppe/go-internal v1.12.0
|
||||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd
|
|
||||||
github.com/sanity-io/litter v1.5.5
|
github.com/sanity-io/litter v1.5.5
|
||||||
github.com/spf13/afero v1.11.0
|
github.com/spf13/afero v1.11.0
|
||||||
github.com/spf13/cast v1.6.0
|
github.com/spf13/cast v1.6.0
|
||||||
|
|
7
go.sum
7
go.sum
|
@ -118,10 +118,6 @@ github.com/bep/clocks v0.5.0 h1:hhvKVGLPQWRVsBP/UB7ErrHYIO42gINVbvqxvYTPVps=
|
||||||
github.com/bep/clocks v0.5.0/go.mod h1:SUq3q+OOq41y2lRQqH5fsOoxN8GbxSiT6jvoVVLCVhU=
|
github.com/bep/clocks v0.5.0/go.mod h1:SUq3q+OOq41y2lRQqH5fsOoxN8GbxSiT6jvoVVLCVhU=
|
||||||
github.com/bep/debounce v1.2.0 h1:wXds8Kq8qRfwAOpAxHrJDbCXgC5aHSzgQb/0gKsHQqo=
|
github.com/bep/debounce v1.2.0 h1:wXds8Kq8qRfwAOpAxHrJDbCXgC5aHSzgQb/0gKsHQqo=
|
||||||
github.com/bep/debounce v1.2.0/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
|
github.com/bep/debounce v1.2.0/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
|
||||||
github.com/bep/gitmap v1.4.0 h1:GeWbPb2QDTfcZLBQmCB693N3sJmPQfeu81fDrD5r8x8=
|
|
||||||
github.com/bep/gitmap v1.4.0/go.mod h1:n+3W1f/rot2hynsqEGxGMErPRgT41n9CkGuzPvz9cIw=
|
|
||||||
github.com/bep/gitmap v1.5.0 h1:ExDl7HeDaRDG8FXFRTnv20qzbyJlC6ivdOboMYFvrms=
|
|
||||||
github.com/bep/gitmap v1.5.0/go.mod h1:n+3W1f/rot2hynsqEGxGMErPRgT41n9CkGuzPvz9cIw=
|
|
||||||
github.com/bep/gitmap v1.6.0 h1:sDuQMm9HoTL0LtlrfxjbjgAg2wHQd4nkMup2FInYzhA=
|
github.com/bep/gitmap v1.6.0 h1:sDuQMm9HoTL0LtlrfxjbjgAg2wHQd4nkMup2FInYzhA=
|
||||||
github.com/bep/gitmap v1.6.0/go.mod h1:n+3W1f/rot2hynsqEGxGMErPRgT41n9CkGuzPvz9cIw=
|
github.com/bep/gitmap v1.6.0/go.mod h1:n+3W1f/rot2hynsqEGxGMErPRgT41n9CkGuzPvz9cIw=
|
||||||
github.com/bep/goat v0.5.0 h1:S8jLXHCVy/EHIoCY+btKkmcxcXFd34a0Q63/0D4TKeA=
|
github.com/bep/goat v0.5.0 h1:S8jLXHCVy/EHIoCY+btKkmcxcXFd34a0Q63/0D4TKeA=
|
||||||
|
@ -136,6 +132,8 @@ github.com/bep/gowebp v0.3.0 h1:MhmMrcf88pUY7/PsEhMgEP0T6fDUnRTMpN8OclDrbrY=
|
||||||
github.com/bep/gowebp v0.3.0/go.mod h1:ZhFodwdiFp8ehGJpF4LdPl6unxZm9lLFjxD3z2h2AgI=
|
github.com/bep/gowebp v0.3.0/go.mod h1:ZhFodwdiFp8ehGJpF4LdPl6unxZm9lLFjxD3z2h2AgI=
|
||||||
github.com/bep/helpers v0.4.0 h1:ab9veaAiWY4ST48Oxp5usaqivDmYdB744fz+tcZ3Ifs=
|
github.com/bep/helpers v0.4.0 h1:ab9veaAiWY4ST48Oxp5usaqivDmYdB744fz+tcZ3Ifs=
|
||||||
github.com/bep/helpers v0.4.0/go.mod h1:/QpHdmcPagDw7+RjkLFCvnlUc8lQ5kg4KDrEkb2Yyco=
|
github.com/bep/helpers v0.4.0/go.mod h1:/QpHdmcPagDw7+RjkLFCvnlUc8lQ5kg4KDrEkb2Yyco=
|
||||||
|
github.com/bep/imagemeta v0.7.0 h1:I6Ve/UToNRdnh8qOlpuiR8dX56q6qi97hOqReaMsLMk=
|
||||||
|
github.com/bep/imagemeta v0.7.0/go.mod h1:5piPAq5Qomh07m/dPPCLN3mDJyFusvUG7VwdRD/vX0s=
|
||||||
github.com/bep/lazycache v0.4.0 h1:X8yVyWNVupPd4e1jV7efi3zb7ZV/qcjKQgIQ5aPbkYI=
|
github.com/bep/lazycache v0.4.0 h1:X8yVyWNVupPd4e1jV7efi3zb7ZV/qcjKQgIQ5aPbkYI=
|
||||||
github.com/bep/lazycache v0.4.0/go.mod h1:NmRm7Dexh3pmR1EignYR8PjO2cWybFQ68+QgY3VMCSc=
|
github.com/bep/lazycache v0.4.0/go.mod h1:NmRm7Dexh3pmR1EignYR8PjO2cWybFQ68+QgY3VMCSc=
|
||||||
github.com/bep/logg v0.4.0 h1:luAo5mO4ZkhA5M1iDVDqDqnBBnlHjmtZF6VAyTp+nCQ=
|
github.com/bep/logg v0.4.0 h1:luAo5mO4ZkhA5M1iDVDqDqnBBnlHjmtZF6VAyTp+nCQ=
|
||||||
|
@ -409,7 +407,6 @@ github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK3dcGsnCnO41eRBOnY12zwkn5qVwgc=
|
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.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo=
|
github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo=
|
||||||
github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U=
|
github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U=
|
||||||
github.com/shogo82148/go-shuffle v0.0.0-20180218125048-27e6095f230d/go.mod h1:2htx6lmL0NGLHlO8ZCf+lQBGBHIbEujyywxJArf+2Yc=
|
github.com/shogo82148/go-shuffle v0.0.0-20180218125048-27e6095f230d/go.mod h1:2htx6lmL0NGLHlO8ZCf+lQBGBHIbEujyywxJArf+2Yc=
|
||||||
|
|
|
@ -82,12 +82,13 @@ SUNSET2: {{ $resized2.RelPermalink }}/{{ $resized2.Width }}/Lat: {{ $resized2.Ex
|
||||||
// Check the file cache
|
// Check the file cache
|
||||||
b.AssertImage(200, 200, "resources/_gen/images/bundle/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x200_resize_q75_box.jpg")
|
b.AssertImage(200, 200, "resources/_gen/images/bundle/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x200_resize_q75_box.jpg")
|
||||||
|
|
||||||
b.AssertFileContent("resources/_gen/images/bundle/sunset_3166614710256882113.json",
|
b.AssertFileContent("resources/_gen/images/bundle/sunset_9750822043026343402.json",
|
||||||
"DateTimeDigitized|time.Time", "PENTAX")
|
"FocalLengthIn35mmFormat|uint16", "PENTAX")
|
||||||
|
|
||||||
b.AssertImage(123, 234, "resources/_gen/images/images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_123x234_resize_q75_box.jpg")
|
b.AssertImage(123, 234, "resources/_gen/images/images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_123x234_resize_q75_box.jpg")
|
||||||
b.AssertFileContent("resources/_gen/images/images/sunset_3166614710256882113.json",
|
|
||||||
"DateTimeDigitized|time.Time", "PENTAX")
|
b.AssertFileContent("resources/_gen/images/images/sunset_9750822043026343402.json",
|
||||||
|
"FocalLengthIn35mmFormat|uint16", "PENTAX")
|
||||||
|
|
||||||
b.AssertNoDuplicateWrites()
|
b.AssertNoDuplicateWrites()
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,8 +82,9 @@ func (i *imageResource) Exif() *exif.ExifInfo {
|
||||||
|
|
||||||
func (i *imageResource) getExif() *exif.ExifInfo {
|
func (i *imageResource) getExif() *exif.ExifInfo {
|
||||||
i.metaInit.Do(func() {
|
i.metaInit.Do(func() {
|
||||||
supportsExif := i.Format == images.JPEG || i.Format == images.TIFF
|
mf := i.Format.ToImageMetaImageFormatFormat()
|
||||||
if !supportsExif {
|
if mf == -1 {
|
||||||
|
// No Exif support for this format.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,7 +115,8 @@ func (i *imageResource) getExif() *exif.ExifInfo {
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
x, err := i.getSpec().imaging.DecodeExif(f)
|
filename := i.getResourcePaths().Path()
|
||||||
|
x, err := i.getSpec().imaging.DecodeExif(filename, mf, f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
i.getSpec().Logger.Warnf("Unable to decode Exif metadata from image: %s", i.Key())
|
i.getSpec().Logger.Warnf("Unable to decode Exif metadata from image: %s", i.Key())
|
||||||
return nil
|
return nil
|
||||||
|
@ -471,7 +473,9 @@ func (i *imageResource) clone(img image.Image) *imageResource {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *imageResource) getImageMetaCacheTargetPath() string {
|
func (i *imageResource) getImageMetaCacheTargetPath() string {
|
||||||
const imageMetaVersionNumber = 1 // Increment to invalidate the meta cache
|
// Increment to invalidate the meta cache
|
||||||
|
// Last increment: v0.130.0 when change to the new imagemeta library for Exif.
|
||||||
|
const imageMetaVersionNumber = 2
|
||||||
|
|
||||||
cfgHash := i.getSpec().imaging.Cfg.SourceHash
|
cfgHash := i.getSpec().imaging.Cfg.SourceHash
|
||||||
df := i.getResourcePaths()
|
df := i.getResourcePaths()
|
||||||
|
|
|
@ -20,22 +20,28 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
qt "github.com/frankban/quicktest"
|
qt "github.com/frankban/quicktest"
|
||||||
|
"github.com/gohugoio/hugo/htesting/hqt"
|
||||||
"github.com/gohugoio/hugo/media"
|
"github.com/gohugoio/hugo/media"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestImageResizeWebP(t *testing.T) {
|
func TestImageResizeWebP(t *testing.T) {
|
||||||
c := qt.New(t)
|
c := qt.New(t)
|
||||||
|
|
||||||
_, image := fetchImage(c, "sunset.webp")
|
_, image := fetchImage(c, "sunrise.webp")
|
||||||
|
|
||||||
c.Assert(image.MediaType(), qt.Equals, media.Builtin.WEBPType)
|
c.Assert(image.MediaType(), qt.Equals, media.Builtin.WEBPType)
|
||||||
c.Assert(image.RelPermalink(), qt.Equals, "/a/sunset.webp")
|
c.Assert(image.RelPermalink(), qt.Equals, "/a/sunrise.webp")
|
||||||
c.Assert(image.ResourceType(), qt.Equals, "image")
|
c.Assert(image.ResourceType(), qt.Equals, "image")
|
||||||
c.Assert(image.Exif(), qt.IsNil)
|
exif := image.Exif()
|
||||||
|
c.Assert(exif, qt.Not(qt.IsNil))
|
||||||
|
c.Assert(exif.Tags["Copyright"], qt.Equals, "Bjørn Erik Pedersen")
|
||||||
|
c.Assert(exif.Lat, hqt.IsSameFloat64, 36.59744166666667)
|
||||||
|
c.Assert(exif.Long, hqt.IsSameFloat64, -4.50846)
|
||||||
|
c.Assert(exif.Date.IsZero(), qt.Equals, false)
|
||||||
|
|
||||||
resized, err := image.Resize("123x")
|
resized, err := image.Resize("123x")
|
||||||
c.Assert(err, qt.IsNil)
|
c.Assert(err, qt.IsNil)
|
||||||
c.Assert(image.MediaType(), qt.Equals, media.Builtin.WEBPType)
|
c.Assert(image.MediaType(), qt.Equals, media.Builtin.WEBPType)
|
||||||
c.Assert(resized.RelPermalink(), qt.Equals, "/a/sunset_hu36ee0b61ba924719ad36da960c273f96_59826_123x0_resize_q68_h2_linear_2.webp")
|
c.Assert(resized.RelPermalink(), qt.Equals, "/a/sunrise_hu6ad68bcbae1b79cbc2f6b451894deaf6_95652_123x0_resize_q68_h2_linear_2.webp")
|
||||||
c.Assert(resized.Width(), qt.Equals, 123)
|
c.Assert(resized.Width(), qt.Equals, 123)
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,6 @@ import (
|
||||||
"image"
|
"image"
|
||||||
"image/gif"
|
"image/gif"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"math/big"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -30,6 +29,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/bep/imagemeta"
|
||||||
"github.com/gohugoio/hugo/htesting"
|
"github.com/gohugoio/hugo/htesting"
|
||||||
"github.com/gohugoio/hugo/resources/images/webp"
|
"github.com/gohugoio/hugo/resources/images/webp"
|
||||||
|
|
||||||
|
@ -67,8 +67,13 @@ var eq = qt.CmpEquals(
|
||||||
return m1.Type == m2.Type
|
return m1.Type == m2.Type
|
||||||
}),
|
}),
|
||||||
cmp.Comparer(
|
cmp.Comparer(
|
||||||
func(v1, v2 *big.Rat) bool {
|
func(v1, v2 imagemeta.Rat[uint32]) bool {
|
||||||
return v1.RatString() == v2.RatString()
|
return v1.String() == v2.String()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
cmp.Comparer(
|
||||||
|
func(v1, v2 imagemeta.Rat[int32]) bool {
|
||||||
|
return v1.String() == v2.String()
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
cmp.Comparer(func(v1, v2 time.Time) bool {
|
cmp.Comparer(func(v1, v2 time.Time) bool {
|
||||||
|
@ -392,7 +397,7 @@ func TestImageResize8BitPNG(t *testing.T) {
|
||||||
c.Assert(image.MediaType().Type, qt.Equals, "image/png")
|
c.Assert(image.MediaType().Type, qt.Equals, "image/png")
|
||||||
c.Assert(image.RelPermalink(), qt.Equals, "/a/gohugoio.png")
|
c.Assert(image.RelPermalink(), qt.Equals, "/a/gohugoio.png")
|
||||||
c.Assert(image.ResourceType(), qt.Equals, "image")
|
c.Assert(image.ResourceType(), qt.Equals, "image")
|
||||||
c.Assert(image.Exif(), qt.IsNil)
|
c.Assert(image.Exif(), qt.IsNotNil)
|
||||||
|
|
||||||
resized, err := image.Resize("800x")
|
resized, err := image.Resize("800x")
|
||||||
c.Assert(err, qt.IsNil)
|
c.Assert(err, qt.IsNil)
|
||||||
|
@ -443,6 +448,7 @@ func TestImageExif(t *testing.T) {
|
||||||
c.Assert(lensModel, qt.Equals, "smc PENTAX-DA* 16-50mm F2.8 ED AL [IF] SDM")
|
c.Assert(lensModel, qt.Equals, "smc PENTAX-DA* 16-50mm F2.8 ED AL [IF] SDM")
|
||||||
resized, _ := image.Resize("300x200")
|
resized, _ := image.Resize("300x200")
|
||||||
x2 := resized.Exif()
|
x2 := resized.Exif()
|
||||||
|
|
||||||
c.Assert(x2, eq, x)
|
c.Assert(x2, eq, x)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,25 +14,18 @@
|
||||||
package exif
|
package exif
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math"
|
|
||||||
"math/big"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"unicode"
|
|
||||||
"unicode/utf8"
|
|
||||||
|
|
||||||
|
"github.com/bep/imagemeta"
|
||||||
|
"github.com/bep/logg"
|
||||||
"github.com/bep/tmc"
|
"github.com/bep/tmc"
|
||||||
|
|
||||||
_exif "github.com/rwcarlsen/goexif/exif"
|
|
||||||
"github.com/rwcarlsen/goexif/tiff"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const exifTimeLayout = "2006:01:02 15:04:05"
|
|
||||||
|
|
||||||
// ExifInfo holds the decoded Exif data for an Image.
|
// ExifInfo holds the decoded Exif data for an Image.
|
||||||
type ExifInfo struct {
|
type ExifInfo struct {
|
||||||
// GPS latitude in degrees.
|
// GPS latitude in degrees.
|
||||||
|
@ -53,6 +46,15 @@ type Decoder struct {
|
||||||
excludeFieldsrRe *regexp.Regexp
|
excludeFieldsrRe *regexp.Regexp
|
||||||
noDate bool
|
noDate bool
|
||||||
noLatLong bool
|
noLatLong bool
|
||||||
|
warnl logg.LevelLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Decoder) shouldInclude(s string) bool {
|
||||||
|
return (d.includeFieldsRe == nil || d.includeFieldsRe.MatchString(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Decoder) shouldExclude(s string) bool {
|
||||||
|
return d.excludeFieldsrRe != nil && d.excludeFieldsrRe.MatchString(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func IncludeFields(expression string) func(*Decoder) error {
|
func IncludeFields(expression string) func(*Decoder) error {
|
||||||
|
@ -91,6 +93,13 @@ func WithDateDisabled(disabled bool) func(*Decoder) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WithWarnLogger(warnl logg.LevelLogger) func(*Decoder) error {
|
||||||
|
return func(d *Decoder) error {
|
||||||
|
d.warnl = warnl
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func compileRegexp(expression string) (*regexp.Regexp, error) {
|
func compileRegexp(expression string) (*regexp.Regexp, error) {
|
||||||
expression = strings.TrimSpace(expression)
|
expression = strings.TrimSpace(expression)
|
||||||
if expression == "" {
|
if expression == "" {
|
||||||
|
@ -115,148 +124,222 @@ func NewDecoder(options ...func(*Decoder) error) (*Decoder, error) {
|
||||||
return d, nil
|
return d, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Decoder) Decode(r io.Reader) (ex *ExifInfo, err error) {
|
var (
|
||||||
|
isTimeTag = func(s string) bool {
|
||||||
|
return strings.Contains(s, "Time")
|
||||||
|
}
|
||||||
|
isGPSTag = func(s string) bool {
|
||||||
|
return strings.HasPrefix(s, "GPS")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Filename is only used for logging.
|
||||||
|
func (d *Decoder) Decode(filename string, format imagemeta.ImageFormat, r io.Reader) (ex *ExifInfo, err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
err = fmt.Errorf("exif failed: %v", r)
|
err = fmt.Errorf("exif failed: %v", r)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var x *_exif.Exif
|
var tagInfos imagemeta.Tags
|
||||||
x, err = _exif.Decode(r)
|
handleTag := func(ti imagemeta.TagInfo) error {
|
||||||
if err != nil {
|
tagInfos.Add(ti)
|
||||||
if err.Error() == "EOF" {
|
return nil
|
||||||
// Found no Exif
|
|
||||||
return nil, nil
|
|
||||||
}
|
}
|
||||||
return
|
|
||||||
|
shouldInclude := func(ti imagemeta.TagInfo) bool {
|
||||||
|
if ti.Source == imagemeta.EXIF {
|
||||||
|
if !d.noDate {
|
||||||
|
// We need the time tags to calculate the date.
|
||||||
|
if isTimeTag(ti.Tag) {
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if !d.noLatLong {
|
||||||
|
// We need to GPS tags to calculate the lat/long.
|
||||||
|
if isGPSTag(ti.Tag) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(ti.Namespace, "IFD0") {
|
||||||
|
// Drop thumbnail tags.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.shouldExclude(ti.Tag) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.shouldInclude(ti.Tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
var warnf func(string, ...any)
|
||||||
|
if d.warnl != nil {
|
||||||
|
// There should be very little warnings (fingers crossed!),
|
||||||
|
// but this will typically be unrecognized formats.
|
||||||
|
// To be able to possibly get rid of these warnings,
|
||||||
|
// we need to know what images are causing them.
|
||||||
|
warnf = func(format string, args ...any) {
|
||||||
|
format = fmt.Sprintf("%q: %s: ", filename, format)
|
||||||
|
d.warnl.Logf(format, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = imagemeta.Decode(
|
||||||
|
imagemeta.Options{
|
||||||
|
R: r.(io.ReadSeeker),
|
||||||
|
ImageFormat: format,
|
||||||
|
ShouldHandleTag: shouldInclude,
|
||||||
|
HandleTag: handleTag,
|
||||||
|
Sources: imagemeta.EXIF, // For now. TODO(bep)
|
||||||
|
Warnf: warnf,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
var tm time.Time
|
var tm time.Time
|
||||||
var lat, long float64
|
var lat, long float64
|
||||||
|
|
||||||
if !d.noDate {
|
if !d.noDate {
|
||||||
tm, _ = x.DateTime()
|
tm, _ = tagInfos.GetDateTime()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !d.noLatLong {
|
if !d.noLatLong {
|
||||||
lat, long, _ = x.LatLong()
|
lat, long, _ = tagInfos.GetLatLong()
|
||||||
if math.IsNaN(lat) {
|
|
||||||
lat = 0
|
|
||||||
}
|
|
||||||
if math.IsNaN(long) {
|
|
||||||
long = 0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
walker := &exifWalker{x: x, vals: make(map[string]any), includeMatcher: d.includeFieldsRe, excludeMatcher: d.excludeFieldsrRe}
|
tags := make(map[string]any)
|
||||||
if err = x.Walk(walker); err != nil {
|
for k, v := range tagInfos.All() {
|
||||||
return
|
if d.shouldExclude(k) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !d.shouldInclude(k) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tags[k] = v.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
ex = &ExifInfo{Lat: lat, Long: long, Date: tm, Tags: walker.vals}
|
ex = &ExifInfo{Lat: lat, Long: long, Date: tm, Tags: tags}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeTag(x *_exif.Exif, f _exif.FieldName, t *tiff.Tag) (any, error) {
|
|
||||||
switch t.Format() {
|
|
||||||
case tiff.StringVal, tiff.UndefVal:
|
|
||||||
s := nullString(t.Val)
|
|
||||||
if strings.Contains(string(f), "DateTime") {
|
|
||||||
if d, err := tryParseDate(x, s); err == nil {
|
|
||||||
return d, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return s, nil
|
|
||||||
case tiff.OtherVal:
|
|
||||||
return "unknown", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var rv []any
|
|
||||||
|
|
||||||
for i := 0; i < int(t.Count); i++ {
|
|
||||||
switch t.Format() {
|
|
||||||
case tiff.RatVal:
|
|
||||||
n, d, _ := t.Rat2(i)
|
|
||||||
rat := big.NewRat(n, d)
|
|
||||||
// if t is int or t > 1, use float64
|
|
||||||
if rat.IsInt() || rat.Cmp(big.NewRat(1, 1)) == 1 {
|
|
||||||
f, _ := rat.Float64()
|
|
||||||
rv = append(rv, f)
|
|
||||||
} else {
|
|
||||||
rv = append(rv, rat)
|
|
||||||
}
|
|
||||||
|
|
||||||
case tiff.FloatVal:
|
|
||||||
v, _ := t.Float(i)
|
|
||||||
rv = append(rv, v)
|
|
||||||
case tiff.IntVal:
|
|
||||||
v, _ := t.Int(i)
|
|
||||||
rv = append(rv, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.Count == 1 {
|
|
||||||
if len(rv) == 1 {
|
|
||||||
return rv[0], nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return rv, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Code borrowed from exif.DateTime and adjusted.
|
|
||||||
func tryParseDate(x *_exif.Exif, s string) (time.Time, error) {
|
|
||||||
dateStr := strings.TrimRight(s, "\x00")
|
|
||||||
// TODO(bep): look for timezone offset, GPS time, etc.
|
|
||||||
timeZone := time.Local
|
|
||||||
if tz, _ := x.TimeZone(); tz != nil {
|
|
||||||
timeZone = tz
|
|
||||||
}
|
|
||||||
return time.ParseInLocation(exifTimeLayout, dateStr, timeZone)
|
|
||||||
}
|
|
||||||
|
|
||||||
type exifWalker struct {
|
|
||||||
x *_exif.Exif
|
|
||||||
vals map[string]any
|
|
||||||
includeMatcher *regexp.Regexp
|
|
||||||
excludeMatcher *regexp.Regexp
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *exifWalker) Walk(f _exif.FieldName, tag *tiff.Tag) error {
|
|
||||||
name := string(f)
|
|
||||||
if e.excludeMatcher != nil && e.excludeMatcher.MatchString(name) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if e.includeMatcher != nil && !e.includeMatcher.MatchString(name) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
val, err := decodeTag(e.x, f, tag)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
e.vals[name] = val
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func nullString(in []byte) string {
|
|
||||||
var rv bytes.Buffer
|
|
||||||
for len(in) > 0 {
|
|
||||||
r, size := utf8.DecodeRune(in)
|
|
||||||
if unicode.IsGraphic(r) {
|
|
||||||
rv.WriteRune(r)
|
|
||||||
}
|
|
||||||
in = in[size:]
|
|
||||||
}
|
|
||||||
return rv.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
var tcodec *tmc.Codec
|
var tcodec *tmc.Codec
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
newIntadapter := func(target any) tmc.Adapter {
|
||||||
|
var bitSize int
|
||||||
|
var isSigned bool
|
||||||
|
|
||||||
|
switch target.(type) {
|
||||||
|
case int:
|
||||||
|
bitSize = 0
|
||||||
|
isSigned = true
|
||||||
|
case int8:
|
||||||
|
bitSize = 8
|
||||||
|
isSigned = true
|
||||||
|
case int16:
|
||||||
|
bitSize = 16
|
||||||
|
isSigned = true
|
||||||
|
case int32:
|
||||||
|
bitSize = 32
|
||||||
|
isSigned = true
|
||||||
|
case int64:
|
||||||
|
bitSize = 64
|
||||||
|
isSigned = true
|
||||||
|
case uint:
|
||||||
|
bitSize = 0
|
||||||
|
case uint8:
|
||||||
|
bitSize = 8
|
||||||
|
case uint16:
|
||||||
|
bitSize = 16
|
||||||
|
case uint32:
|
||||||
|
bitSize = 32
|
||||||
|
case uint64:
|
||||||
|
bitSize = 64
|
||||||
|
}
|
||||||
|
|
||||||
|
intFromString := func(s string) (any, error) {
|
||||||
|
if bitSize == 0 {
|
||||||
|
return strconv.Atoi(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
var v any
|
||||||
var err error
|
var err error
|
||||||
tcodec, err = tmc.New()
|
|
||||||
|
if isSigned {
|
||||||
|
v, err = strconv.ParseInt(s, 10, bitSize)
|
||||||
|
} else {
|
||||||
|
v, err = strconv.ParseUint(s, 10, bitSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if isSigned {
|
||||||
|
i := v.(int64)
|
||||||
|
switch target.(type) {
|
||||||
|
case int:
|
||||||
|
return int(i), nil
|
||||||
|
case int8:
|
||||||
|
return int8(i), nil
|
||||||
|
case int16:
|
||||||
|
return int16(i), nil
|
||||||
|
case int32:
|
||||||
|
return int32(i), nil
|
||||||
|
case int64:
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i := v.(uint64)
|
||||||
|
switch target.(type) {
|
||||||
|
case uint:
|
||||||
|
return uint(i), nil
|
||||||
|
case uint8:
|
||||||
|
return uint8(i), nil
|
||||||
|
case uint16:
|
||||||
|
return uint16(i), nil
|
||||||
|
case uint32:
|
||||||
|
return uint32(i), nil
|
||||||
|
case uint64:
|
||||||
|
return i, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, fmt.Errorf("unsupported target type %T", target)
|
||||||
|
}
|
||||||
|
|
||||||
|
intToString := func(v any) (string, error) {
|
||||||
|
return fmt.Sprintf("%d", v), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return tmc.NewAdapter(target, intFromString, intToString)
|
||||||
|
}
|
||||||
|
|
||||||
|
ru, _ := imagemeta.NewRat[uint32](1, 2)
|
||||||
|
ri, _ := imagemeta.NewRat[int32](1, 2)
|
||||||
|
tmcAdapters := []tmc.Adapter{
|
||||||
|
tmc.NewAdapter(ru, nil, nil),
|
||||||
|
tmc.NewAdapter(ri, nil, nil),
|
||||||
|
newIntadapter(int(1)),
|
||||||
|
newIntadapter(int8(1)),
|
||||||
|
newIntadapter(int16(1)),
|
||||||
|
newIntadapter(int32(1)),
|
||||||
|
newIntadapter(int64(1)),
|
||||||
|
newIntadapter(uint(1)),
|
||||||
|
newIntadapter(uint8(1)),
|
||||||
|
newIntadapter(uint16(1)),
|
||||||
|
newIntadapter(uint32(1)),
|
||||||
|
newIntadapter(uint64(1)),
|
||||||
|
}
|
||||||
|
|
||||||
|
tmcAdapters = append(tmc.DefaultTypeAdapters, tmcAdapters...)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
tcodec, err = tmc.New(tmc.WithTypeAdapters(tmcAdapters))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,13 +15,12 @@ package exif
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"math/big"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/htesting/hqt"
|
"github.com/bep/imagemeta"
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
|
|
||||||
qt "github.com/frankban/quicktest"
|
qt "github.com/frankban/quicktest"
|
||||||
|
@ -35,11 +34,12 @@ func TestExif(t *testing.T) {
|
||||||
|
|
||||||
d, err := NewDecoder(IncludeFields("Lens|Date"))
|
d, err := NewDecoder(IncludeFields("Lens|Date"))
|
||||||
c.Assert(err, qt.IsNil)
|
c.Assert(err, qt.IsNil)
|
||||||
x, err := d.Decode(f)
|
x, err := d.Decode("", imagemeta.JPEG, f)
|
||||||
c.Assert(err, qt.IsNil)
|
c.Assert(err, qt.IsNil)
|
||||||
c.Assert(x.Date.Format("2006-01-02"), qt.Equals, "2017-10-27")
|
c.Assert(x.Date.Format("2006-01-02"), qt.Equals, "2017-10-27")
|
||||||
|
|
||||||
// Malaga: https://goo.gl/taazZy
|
// Malaga: https://goo.gl/taazZy
|
||||||
|
|
||||||
c.Assert(x.Lat, qt.Equals, float64(36.59744166666667))
|
c.Assert(x.Lat, qt.Equals, float64(36.59744166666667))
|
||||||
c.Assert(x.Long, qt.Equals, float64(-4.50846))
|
c.Assert(x.Long, qt.Equals, float64(-4.50846))
|
||||||
|
|
||||||
|
@ -49,9 +49,9 @@ func TestExif(t *testing.T) {
|
||||||
c.Assert(ok, qt.Equals, true)
|
c.Assert(ok, qt.Equals, true)
|
||||||
c.Assert(lensModel, qt.Equals, "smc PENTAX-DA* 16-50mm F2.8 ED AL [IF] SDM")
|
c.Assert(lensModel, qt.Equals, "smc PENTAX-DA* 16-50mm F2.8 ED AL [IF] SDM")
|
||||||
|
|
||||||
v, found = x.Tags["DateTime"]
|
v, found = x.Tags["ModifyDate"]
|
||||||
c.Assert(found, qt.Equals, true)
|
c.Assert(found, qt.Equals, true)
|
||||||
c.Assert(v, hqt.IsSameType, time.Time{})
|
c.Assert(v, qt.Equals, "2017:11:23 09:56:54")
|
||||||
|
|
||||||
// Verify that it survives a round-trip to JSON and back.
|
// Verify that it survives a round-trip to JSON and back.
|
||||||
data, err := json.Marshal(x)
|
data, err := json.Marshal(x)
|
||||||
|
@ -72,8 +72,8 @@ func TestExifPNG(t *testing.T) {
|
||||||
|
|
||||||
d, err := NewDecoder()
|
d, err := NewDecoder()
|
||||||
c.Assert(err, qt.IsNil)
|
c.Assert(err, qt.IsNil)
|
||||||
_, err = d.Decode(f)
|
_, err = d.Decode("", imagemeta.PNG, f)
|
||||||
c.Assert(err, qt.Not(qt.IsNil))
|
c.Assert(err, qt.IsNil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIssue8079(t *testing.T) {
|
func TestIssue8079(t *testing.T) {
|
||||||
|
@ -85,28 +85,11 @@ func TestIssue8079(t *testing.T) {
|
||||||
|
|
||||||
d, err := NewDecoder()
|
d, err := NewDecoder()
|
||||||
c.Assert(err, qt.IsNil)
|
c.Assert(err, qt.IsNil)
|
||||||
x, err := d.Decode(f)
|
x, err := d.Decode("", imagemeta.JPEG, f)
|
||||||
c.Assert(err, qt.IsNil)
|
c.Assert(err, qt.IsNil)
|
||||||
c.Assert(x.Tags["ImageDescription"], qt.Equals, "Città del Vaticano #nanoblock #vatican #vaticancity")
|
c.Assert(x.Tags["ImageDescription"], qt.Equals, "Città del Vaticano #nanoblock #vatican #vaticancity")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNullString(t *testing.T) {
|
|
||||||
c := qt.New(t)
|
|
||||||
|
|
||||||
for _, test := range []struct {
|
|
||||||
in string
|
|
||||||
expect string
|
|
||||||
}{
|
|
||||||
{"foo", "foo"},
|
|
||||||
{"\x20", "\x20"},
|
|
||||||
{"\xc4\x81", "\xc4\x81"}, // \u0101
|
|
||||||
{"\u0160", "\u0160"}, // non-breaking space
|
|
||||||
} {
|
|
||||||
res := nullString([]byte(test.in))
|
|
||||||
c.Assert(res, qt.Equals, test.expect)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkDecodeExif(b *testing.B) {
|
func BenchmarkDecodeExif(b *testing.B) {
|
||||||
c := qt.New(b)
|
c := qt.New(b)
|
||||||
f, err := os.Open(filepath.FromSlash("../../testdata/sunset.jpg"))
|
f, err := os.Open(filepath.FromSlash("../../testdata/sunset.jpg"))
|
||||||
|
@ -118,7 +101,7 @@ func BenchmarkDecodeExif(b *testing.B) {
|
||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
_, err = d.Decode(f)
|
_, err = d.Decode("", imagemeta.JPEG, f)
|
||||||
c.Assert(err, qt.IsNil)
|
c.Assert(err, qt.IsNil)
|
||||||
f.Seek(0, 0)
|
f.Seek(0, 0)
|
||||||
}
|
}
|
||||||
|
@ -126,8 +109,13 @@ func BenchmarkDecodeExif(b *testing.B) {
|
||||||
|
|
||||||
var eq = qt.CmpEquals(
|
var eq = qt.CmpEquals(
|
||||||
cmp.Comparer(
|
cmp.Comparer(
|
||||||
func(v1, v2 *big.Rat) bool {
|
func(v1, v2 imagemeta.Rat[uint32]) bool {
|
||||||
return v1.RatString() == v2.RatString()
|
return v1.String() == v2.String()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
cmp.Comparer(
|
||||||
|
func(v1, v2 imagemeta.Rat[int32]) bool {
|
||||||
|
return v1.String() == v2.String()
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
cmp.Comparer(func(v1, v2 time.Time) bool {
|
cmp.Comparer(func(v1, v2 time.Time) bool {
|
||||||
|
@ -138,14 +126,15 @@ var eq = qt.CmpEquals(
|
||||||
func TestIssue10738(t *testing.T) {
|
func TestIssue10738(t *testing.T) {
|
||||||
c := qt.New(t)
|
c := qt.New(t)
|
||||||
|
|
||||||
testFunc := func(path, include string) any {
|
testFunc := func(c *qt.C, path, include string) any {
|
||||||
|
c.Helper()
|
||||||
f, err := os.Open(filepath.FromSlash(path))
|
f, err := os.Open(filepath.FromSlash(path))
|
||||||
c.Assert(err, qt.IsNil)
|
c.Assert(err, qt.IsNil)
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
d, err := NewDecoder(IncludeFields(include))
|
d, err := NewDecoder(IncludeFields(include))
|
||||||
c.Assert(err, qt.IsNil)
|
c.Assert(err, qt.IsNil)
|
||||||
x, err := d.Decode(f)
|
x, err := d.Decode("", imagemeta.JPEG, f)
|
||||||
c.Assert(err, qt.IsNil)
|
c.Assert(err, qt.IsNil)
|
||||||
|
|
||||||
// Verify that it survives a round-trip to JSON and back.
|
// Verify that it survives a round-trip to JSON and back.
|
||||||
|
@ -194,7 +183,7 @@ func TestIssue10738(t *testing.T) {
|
||||||
include: "Lens|Date|ExposureTime",
|
include: "Lens|Date|ExposureTime",
|
||||||
}, want{
|
}, want{
|
||||||
10,
|
10,
|
||||||
0,
|
1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -221,7 +210,7 @@ func TestIssue10738(t *testing.T) {
|
||||||
include: "Lens|Date|ExposureTime",
|
include: "Lens|Date|ExposureTime",
|
||||||
}, want{
|
}, want{
|
||||||
1,
|
1,
|
||||||
0,
|
1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -266,7 +255,7 @@ func TestIssue10738(t *testing.T) {
|
||||||
include: "Lens|Date|ExposureTime",
|
include: "Lens|Date|ExposureTime",
|
||||||
}, want{
|
}, want{
|
||||||
30,
|
30,
|
||||||
0,
|
1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -293,19 +282,21 @@ func TestIssue10738(t *testing.T) {
|
||||||
include: "Lens|Date|ExposureTime",
|
include: "Lens|Date|ExposureTime",
|
||||||
}, want{
|
}, want{
|
||||||
4,
|
4,
|
||||||
0,
|
1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
c.Run(tt.name, func(c *qt.C) {
|
c.Run(tt.name, func(c *qt.C) {
|
||||||
got := testFunc(tt.args.path, tt.args.include)
|
got := testFunc(c, tt.args.path, tt.args.include)
|
||||||
switch v := got.(type) {
|
switch v := got.(type) {
|
||||||
case float64:
|
case float64:
|
||||||
c.Assert(v, qt.Equals, float64(tt.want.vN))
|
c.Assert(v, qt.Equals, float64(tt.want.vN))
|
||||||
case *big.Rat:
|
case imagemeta.Rat[uint32]:
|
||||||
c.Assert(v, eq, big.NewRat(tt.want.vN, tt.want.vD))
|
r, err := imagemeta.NewRat[uint32](uint32(tt.want.vN), uint32(tt.want.vD))
|
||||||
|
c.Assert(err, qt.IsNil)
|
||||||
|
c.Assert(v, eq, r)
|
||||||
default:
|
default:
|
||||||
c.Fatalf("unexpected type: %T", got)
|
c.Fatalf("unexpected type: %T", got)
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,8 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/bep/gowebp/libwebp/webpoptions"
|
"github.com/bep/gowebp/libwebp/webpoptions"
|
||||||
|
"github.com/bep/imagemeta"
|
||||||
|
"github.com/bep/logg"
|
||||||
"github.com/gohugoio/hugo/config"
|
"github.com/gohugoio/hugo/config"
|
||||||
"github.com/gohugoio/hugo/resources/images/webp"
|
"github.com/gohugoio/hugo/resources/images/webp"
|
||||||
|
|
||||||
|
@ -174,13 +176,14 @@ func (i *Image) initConfig() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewImageProcessor(cfg *config.ConfigNamespace[ImagingConfig, ImagingConfigInternal]) (*ImageProcessor, error) {
|
func NewImageProcessor(warnl logg.LevelLogger, cfg *config.ConfigNamespace[ImagingConfig, ImagingConfigInternal]) (*ImageProcessor, error) {
|
||||||
e := cfg.Config.Imaging.Exif
|
e := cfg.Config.Imaging.Exif
|
||||||
exifDecoder, err := exif.NewDecoder(
|
exifDecoder, err := exif.NewDecoder(
|
||||||
exif.WithDateDisabled(e.DisableDate),
|
exif.WithDateDisabled(e.DisableDate),
|
||||||
exif.WithLatLongDisabled(e.DisableLatLong),
|
exif.WithLatLongDisabled(e.DisableLatLong),
|
||||||
exif.ExcludeFields(e.ExcludeFields),
|
exif.ExcludeFields(e.ExcludeFields),
|
||||||
exif.IncludeFields(e.IncludeFields),
|
exif.IncludeFields(e.IncludeFields),
|
||||||
|
exif.WithWarnLogger(warnl),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -197,8 +200,9 @@ type ImageProcessor struct {
|
||||||
exifDecoder *exif.Decoder
|
exifDecoder *exif.Decoder
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ImageProcessor) DecodeExif(r io.Reader) (*exif.ExifInfo, error) {
|
// Filename is only used for logging.
|
||||||
return p.exifDecoder.Decode(r)
|
func (p *ImageProcessor) DecodeExif(filename string, format imagemeta.ImageFormat, r io.Reader) (*exif.ExifInfo, error) {
|
||||||
|
return p.exifDecoder.Decode(filename, format, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ImageProcessor) FiltersFromConfig(src image.Image, conf ImageConfig) ([]gift.Filter, error) {
|
func (p *ImageProcessor) FiltersFromConfig(src image.Image, conf ImageConfig) ([]gift.Filter, error) {
|
||||||
|
@ -353,6 +357,21 @@ const (
|
||||||
WEBP
|
WEBP
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (f Format) ToImageMetaImageFormatFormat() imagemeta.ImageFormat {
|
||||||
|
switch f {
|
||||||
|
case JPEG:
|
||||||
|
return imagemeta.JPEG
|
||||||
|
case PNG:
|
||||||
|
return imagemeta.PNG
|
||||||
|
case TIFF:
|
||||||
|
return imagemeta.TIFF
|
||||||
|
case WEBP:
|
||||||
|
return imagemeta.WebP
|
||||||
|
default:
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// RequiresDefaultQuality returns if the default quality needs to be applied to
|
// RequiresDefaultQuality returns if the default quality needs to be applied to
|
||||||
// images of this format.
|
// images of this format.
|
||||||
func (f Format) RequiresDefaultQuality() bool {
|
func (f Format) RequiresDefaultQuality() bool {
|
||||||
|
|
|
@ -60,7 +60,9 @@ func NewSpec(
|
||||||
conf := s.Cfg.GetConfig().(*allconfig.Config)
|
conf := s.Cfg.GetConfig().(*allconfig.Config)
|
||||||
imgConfig := conf.Imaging
|
imgConfig := conf.Imaging
|
||||||
|
|
||||||
imaging, err := images.NewImageProcessor(imgConfig)
|
imagesWarnl := logger.WarnCommand("images")
|
||||||
|
|
||||||
|
imaging, err := images.NewImageProcessor(imagesWarnl, imgConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,11 +99,6 @@ func (t *tailwindcssTransformation) Transform(ctx *resources.ResourceTransformat
|
||||||
|
|
||||||
cmdArgs = append(cmdArgs, options.toArgs()...)
|
cmdArgs = append(cmdArgs, options.toArgs()...)
|
||||||
|
|
||||||
// TODO1
|
|
||||||
// npm i tailwindcss @tailwindcss/cli
|
|
||||||
// npm i tailwindcss@next @tailwindcss/cli@next
|
|
||||||
// npx tailwindcss -h
|
|
||||||
|
|
||||||
var errBuf bytes.Buffer
|
var errBuf bytes.Buffer
|
||||||
|
|
||||||
stderr := io.MultiWriter(infow, &errBuf)
|
stderr := io.MultiWriter(infow, &errBuf)
|
||||||
|
@ -134,7 +129,6 @@ func (t *tailwindcssTransformation) Transform(ctx *resources.ResourceTransformat
|
||||||
t.rs.Assets.Fs, t.rs.Logger, ctx.DependencyManager,
|
t.rs.Assets.Fs, t.rs.Logger, ctx.DependencyManager,
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO1 option {
|
|
||||||
src, err = imp.resolve()
|
src, err = imp.resolve()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
BIN
resources/testdata/sunrise.webp
vendored
Normal file
BIN
resources/testdata/sunrise.webp
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 93 KiB |
Loading…
Reference in a new issue