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 }