diff --git a/README.md b/README.md index 66b1f2c1366..0a1fca65ff2 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,7 @@ github.com/cpuguy83/go-md2man/v2="v2.0.0" github.com/danwakefield/fnmatch="v0.0.0-20160403171240-cbb64ac3d964" github.com/disintegration/gift="v1.2.1" github.com/dlclark/regexp2="v1.4.0" +github.com/dsoprea/go-exif/v3="v3.0.0-20210512055020-8213cfabc61b" github.com/dustin/go-humanize="v1.0.0" github.com/evanw/esbuild="v0.11.16" github.com/fsnotify/fsnotify="v1.4.9" diff --git a/go.mod b/go.mod index 81002601663..ba91591bd81 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,9 @@ require ( github.com/bep/tmc v0.5.1 github.com/cli/safeexec v1.0.0 github.com/disintegration/gift v1.2.1 + github.com/dsoprea/go-exif/v3 v3.0.0-20210512055020-8213cfabc61b + github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect + github.com/dsoprea/go-png-image-structure v0.0.0-20210512210324-29b889a6093d github.com/dustin/go-humanize v1.0.0 github.com/evanw/esbuild v0.11.16 github.com/fortytw2/leaktest v1.3.0 @@ -46,7 +49,6 @@ require ( github.com/pkg/errors v0.9.1 github.com/rogpeppe/go-internal v1.8.0 github.com/russross/blackfriday v1.5.3-0.20200218234912-41c5fccfd6f6 - github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd github.com/sanity-io/litter v1.5.0 github.com/spf13/afero v1.6.0 github.com/spf13/cast v1.3.1 diff --git a/go.sum b/go.sum index 5d2c9b2cce2..d7992b04b0f 100644 --- a/go.sum +++ b/go.sum @@ -102,8 +102,6 @@ github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI= github.com/alecthomas/chroma v0.7.2-0.20200305040604-4f3623dce67a/go.mod h1:fv5SzZPFJbwp2NXJWpFIX7DZS4HgV1K4ew4Pc2OZD9s= github.com/alecthomas/chroma v0.8.2/go.mod h1:sko8vR34/90zvl5QdcUdvzL3J8NKjAUx9va9jPuFNoM= -github.com/alecthomas/chroma v0.9.1 h1:cBmvQqRImzR5aWqdMxYZByND4S7BCS/g0svZb28h0Dc= -github.com/alecthomas/chroma v0.9.1/go.mod h1:eMuEnpA18XbG/WhOWtCzJHS7WqEtDAI+HxdwoW0nVSk= github.com/alecthomas/chroma v0.9.2 h1:yU1sE2+TZbLIQPMk30SolL2Hn53SR/Pv750f7qZ/XMs= github.com/alecthomas/chroma v0.9.2/go.mod h1:eMuEnpA18XbG/WhOWtCzJHS7WqEtDAI+HxdwoW0nVSk= github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo= @@ -182,6 +180,23 @@ github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55k github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E= github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dsoprea/go-exif/v2 v2.0.0-20200321225314-640175a69fe4/go.mod h1:Lm2lMM2zx8p4a34ZemkaUV95AnMl4ZvLbCUbwOvLC2E= +github.com/dsoprea/go-exif/v2 v2.0.0-20200604193436-ca8584a0e1c4 h1:Mg7pY7kxDQD2Bkvr1N+XW4BESSIQ7tTTR7Vv+Gi2CsM= +github.com/dsoprea/go-exif/v2 v2.0.0-20200604193436-ca8584a0e1c4/go.mod h1:9EXlPeHfblFFnwu5UOqmP2eoZfJyAZ2Ri/Vki33ajO0= +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-20210512043655-120bcdb2a55e/go.mod h1:cg5SNYKHMmzxsr9X6ZeLh/nfBRHHp5PngtEPcujONtk= +github.com/dsoprea/go-exif/v3 v3.0.0-20210512055020-8213cfabc61b h1:Ry/3m2rXSlKtTTfjfzf6JHmCfVQlR2Kp+hg/sLMDv9M= +github.com/dsoprea/go-exif/v3 v3.0.0-20210512055020-8213cfabc61b/go.mod h1:cg5SNYKHMmzxsr9X6ZeLh/nfBRHHp5PngtEPcujONtk= +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/go.mod h1:7I+3Pe2o/YSU88W0hWlm9S22W7XI1JFNJ86U0zPKMf8= +github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd h1:l+vLbuxptsC6VQyQsfD7NnEC8BZuFpz45PgY+pH8YTg= +github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd/go.mod h1:7I+3Pe2o/YSU88W0hWlm9S22W7XI1JFNJ86U0zPKMf8= +github.com/dsoprea/go-png-image-structure v0.0.0-20210512210324-29b889a6093d h1:8+qI8ant/vZkNSsbwSjIR6XJfWcDVTg/qx/3pRUUZNA= +github.com/dsoprea/go-png-image-structure v0.0.0-20210512210324-29b889a6093d/go.mod h1:yTR3tKgyk20phAFg6IE9ulMA5NjEDD2wyx+okRFLVtw= +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/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.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -208,6 +223,10 @@ github.com/getkin/kin-openapi v0.61.0 h1:6awGqF5nG5zkVpMsAih1QH4VgzS8phTxECUWIFo github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs= +github.com/go-errors/errors v1.1.1 h1:ljK/pL5ltg3qoN+OtN6yCv9HWSfMwxSx90GJCZQxYNg= +github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -231,6 +250,9 @@ github.com/gohugoio/go-i18n/v2 v2.1.3-0.20210430103248-4c28c89f8013 h1:Nj29Qbkt0 github.com/gohugoio/go-i18n/v2 v2.1.3-0.20210430103248-4c28c89f8013/go.mod h1:3Ltoo9Banwq0gOtcOwxuHG6omk+AwsQPADyw2vQYOJQ= github.com/gohugoio/testmodBuilder/mods v0.0.0-20190520184928-c56af20f2e95 h1:sgew0XCnZwnzpWxTt3V8LLiCO7OQi3C6dycaE67wfkU= github.com/gohugoio/testmodBuilder/mods v0.0.0-20190520184928-c56af20f2e95/go.mod h1:bOlVlCa1/RajcHpXkrUXPSHB/Re1UnlXxD1Qp8SKOd8= +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/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -467,8 +489,6 @@ github.com/russross/blackfriday v1.5.3-0.20200218234912-41c5fccfd6f6 h1:tlXG832s github.com/russross/blackfriday v1.5.3-0.20200218234912-41c5fccfd6f6/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/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/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sanity-io/litter v1.5.0 h1:cHM1wTJiOETY9LKRPd3tqUHGquaBaTteD1tZFesEoi8= github.com/sanity-io/litter v1.5.0/go.mod h1:5Z71SvaYy5kcGtyglXOC9rrUi3c1E8CamFWjQsazTh0= @@ -515,12 +535,8 @@ github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/tdewolff/minify/v2 v2.9.16 h1:2Pv8pFRX/ZfjTRYX2xzcuNrkEJqU5TfriNJJYOeN3rI= -github.com/tdewolff/minify/v2 v2.9.16/go.mod h1:cjMkr4ZgFjqxXAQ1kR9Fm4l1046mmONd2g6yMzGuN/w= github.com/tdewolff/minify/v2 v2.9.18 h1:j5Is0sOGp4cxm0o3HgvHCWCvTtmKnfB0qv0FCRbmgZY= github.com/tdewolff/minify/v2 v2.9.18/go.mod h1:0y0mXZnisZm8HcgQvAV0btxa1IgecGam90zMuHqEZuc= -github.com/tdewolff/parse/v2 v2.5.14 h1:ftdD54vkOeLZ7VkEZxp+wZrYZyyPi43GGon5GwBTRUI= -github.com/tdewolff/parse/v2 v2.5.14/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho= github.com/tdewolff/parse/v2 v2.5.18 h1:d67Ql/Pe36JcJZ7J2MY8upx6iTxbxGS9lzwyFGtMmd0= github.com/tdewolff/parse/v2 v2.5.18/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho= github.com/tdewolff/test v1.0.6 h1:76mzYJQ83Op284kMT+63iCNCI7NEERsIN8dLM+RiKr4= @@ -625,6 +641,7 @@ golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200320220750-118fecf932d8/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= @@ -706,7 +723,6 @@ golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -718,7 +734,6 @@ golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210412220455-f1c623a9e750 h1:ZBu6861dZq7xBnG1bn5SRU0vA8nx42at4+kP07FMTog= golang.org/x/sys v0.0.0-20210412220455-f1c623a9e750/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -925,6 +940,7 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= diff --git a/resources/images/exif/exif.go b/resources/images/exif/exif.go index 5406665cfe3..929c25bd378 100644 --- a/resources/images/exif/exif.go +++ b/resources/images/exif/exif.go @@ -14,23 +14,26 @@ package exif import ( - "bytes" "fmt" "io" + "io/ioutil" "math/big" "regexp" "strings" "time" - "unicode" - "unicode/utf8" "github.com/bep/tmc" - _exif "github.com/rwcarlsen/goexif/exif" - "github.com/rwcarlsen/goexif/tiff" + _exif "github.com/dsoprea/go-exif/v3" + exifcommon "github.com/dsoprea/go-exif/v3/common" + png "github.com/dsoprea/go-png-image-structure" ) -const exifTimeLayout = "2006:01:02 15:04:05" +const ( + exifTimeLayout = "2006:01:02 15:04:05" + // IFD ID used for efficiency. ID obtained from https://exiftool.org/TagNames/EXIF.html + dateTimeTagId = 0x9003 +) type Exif struct { Lat float64 @@ -106,134 +109,167 @@ func NewDecoder(options ...func(*Decoder) error) (*Decoder, error) { return d, nil } -func (d *Decoder) Decode(r io.Reader) (ex *Exif, err error) { - defer func() { - if r := recover(); r != nil { - err = fmt.Errorf("Exif failed: %v", r) - } - }() - - var x *_exif.Exif - x, err = _exif.Decode(r) +func (d *Decoder) Decode(r io.Reader) (*Exif, error) { + rawBytes, err := ioutil.ReadAll(r) if err != nil { - if err.Error() == "EOF" { - // Found no Exif - return nil, nil - } - return + return nil, err } + rawExif, err := _exif.SearchAndExtractExif(rawBytes) - var tm time.Time - var lat, long float64 - - if !d.noDate { - tm, _ = x.DateTime() + if err != nil { + parsed := png.NewPngMediaParser() + if parsed.LooksLikeFormat(rawBytes) { + return nil, fmt.Errorf("type not supported") + } + // No Exif data + return nil, nil } - if !d.noLatLong { - lat, long, _ = x.LatLong() + im, err := exifcommon.NewIfdMappingWithStandard() + if err != nil { + return nil, err } + ti := _exif.NewTagIndex() - walker := &exifWalker{x: x, vals: make(map[string]interface{}), includeMatcher: d.includeFieldsRe, excludeMatcher: d.excludeFieldsrRe} - if err = x.Walk(walker); err != nil { - return + _, index, err := _exif.Collect(im, ti, rawExif) + if err != nil { + return nil, err } - ex = &Exif{Lat: lat, Long: long, Date: tm, Tags: walker.vals} + var tm time.Time + var lat, long float64 - return -} + rootIfd := index.RootIfd -func decodeTag(x *_exif.Exif, f _exif.FieldName, t *tiff.Tag) (interface{}, 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 - } + ifd, err := rootIfd.ChildWithIfdPath(exifcommon.IfdGpsInfoStandardIfdIdentity) + if !d.noLatLong && err == nil { + if gpsInfo, err := ifd.GpsInfo(); err == nil { + lat = gpsInfo.Latitude.Decimal() + long = gpsInfo.Longitude.Decimal() } - return s, nil - case tiff.OtherVal: - return "unknown", nil } - var rv []interface{} + exifIfd, _ := rootIfd.ChildWithIfdPath(exifcommon.IfdExifStandardIfdIdentity) - 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 n == 1 { - rv = append(rv, rat) - } else { - f, _ := rat.Float64() - rv = append(rv, f) - } + if results, err := exifIfd.FindTagWithId(dateTimeTagId); !d.noDate && err == nil && len(results) == 1 { + dateTimeTagEntry := results[0] + dateTimeRaw, _ := dateTimeTagEntry.Value() + dateTimeString := dateTimeRaw.(string) - case tiff.FloatVal: - v, _ := t.Float(i) - rv = append(rv, v) - case tiff.IntVal: - v, _ := t.Int(i) - rv = append(rv, v) - } + tm, _ = time.Parse(exifTimeLayout, dateTimeString) } - if t.Count == 1 { - if len(rv) == 1 { - return rv[0], nil - } + vals, err := extractTags(&index, d.includeFieldsRe, d.excludeFieldsrRe) + if err != nil { + // No Exif metadata + return nil, nil } - return rv, nil + ex := &Exif{Lat: lat, Long: long, Date: tm, Tags: vals} + + return ex, nil } // Code borrowed from exif.DateTime and adjusted. -func tryParseDate(x *_exif.Exif, s string) (time.Time, error) { +func tryParseDate(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]interface{} - includeMatcher *regexp.Regexp - excludeMatcher *regexp.Regexp +func processRational(n int64, d int64) interface{} { + rat := big.NewRat(n, d) + if n != 1 { + f, _ := rat.Float64() + return f + } + return rat } -func (e *exifWalker) Walk(f _exif.FieldName, tag *tiff.Tag) error { - name := string(f) - if e.excludeMatcher != nil && e.excludeMatcher.MatchString(name) { - return nil +func extractTagValue(ite *_exif.IfdTagEntry) (val interface{}, err error) { + tagName := ite.TagName() + tagType := ite.TagType() + unitCount := ite.UnitCount() + valueRaw, err := ite.Value() + if err != nil { + return nil, err } - if e.includeMatcher != nil && !e.includeMatcher.MatchString(name) { - return nil + + switch tagType { + case exifcommon.TypeAscii: + s, _ := valueRaw.(string) + if strings.Contains(tagName, "DateTime") { + if d, err := tryParseDate(s); err == nil { + return d, nil + } + } + return s, nil + + case exifcommon.TypeUndefined: + return valueRaw, nil } - val, err := decodeTag(e.x, f, tag) - if err != nil { - return err + + var rv []interface{} + + for i := 0; i < int(unitCount); i++ { + var val interface{} + switch tagType { + + case exifcommon.TypeRational: + vals, _ := valueRaw.([]exifcommon.Rational) + r := vals[i] + val = processRational(int64(r.Numerator), int64(r.Denominator)) + + case exifcommon.TypeSignedRational: + vals, _ := valueRaw.([]exifcommon.SignedRational) + r := vals[i] + val = processRational(int64(r.Numerator), int64(r.Denominator)) + + case exifcommon.TypeShort: + vals, _ := valueRaw.([]uint16) + i := vals[i] + val = int(i) + + case exifcommon.TypeByte: + vals, _ := valueRaw.([]uint8) + val = vals[i] + } + + if val != nil { + rv = append(rv, val) + } } - e.vals[name] = val - return nil + + if unitCount == 1 && len(rv) == 1 { + return rv[0], nil + } + + return rv, 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) +func extractTags(ifdIndex *_exif.IfdIndex, includeMatcher *regexp.Regexp, excludeMatcher *regexp.Regexp) (map[string]interface{}, error) { + vals := make(map[string]interface{}) + err := ifdIndex.RootIfd.EnumerateTagsRecursively(func(ifd *_exif.Ifd, ite *_exif.IfdTagEntry) error { + if ite != nil { + name := ite.TagName() + + if excludeMatcher != nil && excludeMatcher.MatchString(name) { + return nil + } + if includeMatcher != nil && !includeMatcher.MatchString(name) { + return nil + } + val, err := extractTagValue(ite) + if err != nil { + return err + } + vals[name] = val } - in = in[size:] - } - return rv.String() + return nil + }) + + return vals, err } var tcodec *tmc.Codec diff --git a/resources/images/exif/exif_test.go b/resources/images/exif/exif_test.go index 0cb9f670449..555d28c3045 100644 --- a/resources/images/exif/exif_test.go +++ b/resources/images/exif/exif_test.go @@ -62,6 +62,78 @@ func TestExif(t *testing.T) { c.Assert(x2, eq, x) } +func TestImageWithoutExifData(t *testing.T) { + c := qt.New(t) + f, err := os.Open(filepath.FromSlash("../../testdata/sunset_without_exif.jpg")) + c.Assert(err, qt.IsNil) + defer f.Close() + + d, err := NewDecoder(IncludeFields("Lens|Date")) + c.Assert(err, qt.IsNil) + x, err := d.Decode(f) + c.Assert(err, qt.IsNil) + c.Assert(x, qt.IsNil) +} + +func TestTagValueTypes(t *testing.T) { + c := qt.New(t) + f, err := os.Open(filepath.FromSlash("../../testdata/sunset.jpg")) + c.Assert(err, qt.IsNil) + defer f.Close() + + d, err := NewDecoder(IncludeFields("FNumber|ExposureTime|ShutterSpeedValue|Flash|GPSVersionID")) + c.Assert(err, qt.IsNil) + x, err := d.Decode(f) + c.Assert(err, qt.IsNil) + + // Rational with numerator > 1 + v, found := x.Tags["FNumber"] + c.Assert(found, qt.Equals, true) + c.Assert(v, hqt.IsSameType, float64(0)) + + // Rational with numerator = 1 + v, found = x.Tags["ExposureTime"] + c.Assert(found, qt.Equals, true) + c.Assert(v, hqt.IsSameType, &big.Rat{}) + + // Signed + v, found = x.Tags["ShutterSpeedValue"] + c.Assert(found, qt.Equals, true) + c.Assert(v, hqt.IsSameType, float64(0)) + + // Short + v, found = x.Tags["Flash"] + c.Assert(found, qt.Equals, true) + c.Assert(v, hqt.IsSameType, int(0)) + + // Byte array + v, found = x.Tags["GPSVersionID"] + c.Assert(found, qt.Equals, true) + inArr, ok := v.([]interface{}) + c.Assert(ok, qt.Equals, true) + c.Assert(inArr[0], hqt.IsSameType, uint8(0)) +} + +func TestDisableOptions(t *testing.T) { + c := qt.New(t) + f, err := os.Open(filepath.FromSlash("../../testdata/sunset.jpg")) + c.Assert(err, qt.IsNil) + defer f.Close() + + d, err := NewDecoder( + WithDateDisabled(true), + WithLatLongDisabled(true), + ) + c.Assert(err, qt.IsNil) + x, err := d.Decode(f) + c.Assert(err, qt.IsNil) + + c.Assert(x.Date.IsZero(), qt.IsTrue) + + c.Assert(x.Lat, qt.Equals, 0.0) + c.Assert(x.Long, qt.Equals, 0.0) +} + func TestExifPNG(t *testing.T) { c := qt.New(t) @@ -89,23 +161,6 @@ func TestIssue8079(t *testing.T) { 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) { c := qt.New(b) f, err := os.Open(filepath.FromSlash("../../testdata/sunset.jpg")) diff --git a/resources/testdata/sunset_without_exif.jpg b/resources/testdata/sunset_without_exif.jpg new file mode 100644 index 00000000000..c4569a08fe3 Binary files /dev/null and b/resources/testdata/sunset_without_exif.jpg differ