exif.go (5476B)
1 // Copyright 2019 The Hugo Authors. All rights reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13
14 package exif
15
16 import (
17 "bytes"
18 "fmt"
19 "io"
20 "math/big"
21 "regexp"
22 "strings"
23 "time"
24 "unicode"
25 "unicode/utf8"
26
27 "github.com/bep/tmc"
28
29 _exif "github.com/rwcarlsen/goexif/exif"
30 "github.com/rwcarlsen/goexif/tiff"
31 )
32
33 const exifTimeLayout = "2006:01:02 15:04:05"
34
35 // ExifInfo holds the decoded Exif data for an Image.
36 type ExifInfo struct {
37 // GPS latitude in degrees.
38 Lat float64
39
40 // GPS longitude in degrees.
41 Long float64
42
43 // Image creation date/time.
44 Date time.Time
45
46 // A collection of the available Exif tags for this Image.
47 Tags Tags
48 }
49
50 type Decoder struct {
51 includeFieldsRe *regexp.Regexp
52 excludeFieldsrRe *regexp.Regexp
53 noDate bool
54 noLatLong bool
55 }
56
57 func IncludeFields(expression string) func(*Decoder) error {
58 return func(d *Decoder) error {
59 re, err := compileRegexp(expression)
60 if err != nil {
61 return err
62 }
63 d.includeFieldsRe = re
64 return nil
65 }
66 }
67
68 func ExcludeFields(expression string) func(*Decoder) error {
69 return func(d *Decoder) error {
70 re, err := compileRegexp(expression)
71 if err != nil {
72 return err
73 }
74 d.excludeFieldsrRe = re
75 return nil
76 }
77 }
78
79 func WithLatLongDisabled(disabled bool) func(*Decoder) error {
80 return func(d *Decoder) error {
81 d.noLatLong = disabled
82 return nil
83 }
84 }
85
86 func WithDateDisabled(disabled bool) func(*Decoder) error {
87 return func(d *Decoder) error {
88 d.noDate = disabled
89 return nil
90 }
91 }
92
93 func compileRegexp(expression string) (*regexp.Regexp, error) {
94 expression = strings.TrimSpace(expression)
95 if expression == "" {
96 return nil, nil
97 }
98 if !strings.HasPrefix(expression, "(") {
99 // Make it case insensitive
100 expression = "(?i)" + expression
101 }
102
103 return regexp.Compile(expression)
104 }
105
106 func NewDecoder(options ...func(*Decoder) error) (*Decoder, error) {
107 d := &Decoder{}
108 for _, opt := range options {
109 if err := opt(d); err != nil {
110 return nil, err
111 }
112 }
113
114 return d, nil
115 }
116
117 func (d *Decoder) Decode(r io.Reader) (ex *ExifInfo, err error) {
118 defer func() {
119 if r := recover(); r != nil {
120 err = fmt.Errorf("Exif failed: %v", r)
121 }
122 }()
123
124 var x *_exif.Exif
125 x, err = _exif.Decode(r)
126 if err != nil {
127 if err.Error() == "EOF" {
128 // Found no Exif
129 return nil, nil
130 }
131 return
132 }
133
134 var tm time.Time
135 var lat, long float64
136
137 if !d.noDate {
138 tm, _ = x.DateTime()
139 }
140
141 if !d.noLatLong {
142 lat, long, _ = x.LatLong()
143 }
144
145 walker := &exifWalker{x: x, vals: make(map[string]any), includeMatcher: d.includeFieldsRe, excludeMatcher: d.excludeFieldsrRe}
146 if err = x.Walk(walker); err != nil {
147 return
148 }
149
150 ex = &ExifInfo{Lat: lat, Long: long, Date: tm, Tags: walker.vals}
151
152 return
153 }
154
155 func decodeTag(x *_exif.Exif, f _exif.FieldName, t *tiff.Tag) (any, error) {
156 switch t.Format() {
157 case tiff.StringVal, tiff.UndefVal:
158 s := nullString(t.Val)
159 if strings.Contains(string(f), "DateTime") {
160 if d, err := tryParseDate(x, s); err == nil {
161 return d, nil
162 }
163 }
164 return s, nil
165 case tiff.OtherVal:
166 return "unknown", nil
167 }
168
169 var rv []any
170
171 for i := 0; i < int(t.Count); i++ {
172 switch t.Format() {
173 case tiff.RatVal:
174 n, d, _ := t.Rat2(i)
175 rat := big.NewRat(n, d)
176 if n == 1 {
177 rv = append(rv, rat)
178 } else {
179 f, _ := rat.Float64()
180 rv = append(rv, f)
181 }
182
183 case tiff.FloatVal:
184 v, _ := t.Float(i)
185 rv = append(rv, v)
186 case tiff.IntVal:
187 v, _ := t.Int(i)
188 rv = append(rv, v)
189 }
190 }
191
192 if t.Count == 1 {
193 if len(rv) == 1 {
194 return rv[0], nil
195 }
196 }
197
198 return rv, nil
199 }
200
201 // Code borrowed from exif.DateTime and adjusted.
202 func tryParseDate(x *_exif.Exif, s string) (time.Time, error) {
203 dateStr := strings.TrimRight(s, "\x00")
204 // TODO(bep): look for timezone offset, GPS time, etc.
205 timeZone := time.Local
206 if tz, _ := x.TimeZone(); tz != nil {
207 timeZone = tz
208 }
209 return time.ParseInLocation(exifTimeLayout, dateStr, timeZone)
210 }
211
212 type exifWalker struct {
213 x *_exif.Exif
214 vals map[string]any
215 includeMatcher *regexp.Regexp
216 excludeMatcher *regexp.Regexp
217 }
218
219 func (e *exifWalker) Walk(f _exif.FieldName, tag *tiff.Tag) error {
220 name := string(f)
221 if e.excludeMatcher != nil && e.excludeMatcher.MatchString(name) {
222 return nil
223 }
224 if e.includeMatcher != nil && !e.includeMatcher.MatchString(name) {
225 return nil
226 }
227 val, err := decodeTag(e.x, f, tag)
228 if err != nil {
229 return err
230 }
231 e.vals[name] = val
232 return nil
233 }
234
235 func nullString(in []byte) string {
236 var rv bytes.Buffer
237 for len(in) > 0 {
238 r, size := utf8.DecodeRune(in)
239 if unicode.IsGraphic(r) {
240 rv.WriteRune(r)
241 }
242 in = in[size:]
243 }
244 return rv.String()
245 }
246
247 var tcodec *tmc.Codec
248
249 func init() {
250 var err error
251 tcodec, err = tmc.New()
252 if err != nil {
253 panic(err)
254 }
255 }
256
257 type Tags map[string]any
258
259 func (v *Tags) UnmarshalJSON(b []byte) error {
260 vv := make(map[string]any)
261 if err := tcodec.Unmarshal(b, &vv); err != nil {
262 return err
263 }
264
265 *v = vv
266
267 return nil
268 }
269
270 func (v Tags) MarshalJSON() ([]byte, error) {
271 return tcodec.Marshal(v)
272 }