github.com/neohugo/neohugo@v0.123.8/resources/images/exif/exif.go (about) 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 t is int or t > 1, use float64 177 if rat.IsInt() || rat.Cmp(big.NewRat(1, 1)) == 1 { 178 f, _ := rat.Float64() 179 rv = append(rv, f) 180 } else { 181 rv = append(rv, rat) 182 } 183 184 case tiff.FloatVal: 185 v, _ := t.Float(i) 186 rv = append(rv, v) 187 case tiff.IntVal: 188 v, _ := t.Int(i) 189 rv = append(rv, v) 190 } 191 } 192 193 if t.Count == 1 { 194 if len(rv) == 1 { 195 return rv[0], nil 196 } 197 } 198 199 return rv, nil 200 } 201 202 // Code borrowed from exif.DateTime and adjusted. 203 func tryParseDate(x *_exif.Exif, s string) (time.Time, error) { 204 dateStr := strings.TrimRight(s, "\x00") 205 // TODO(bep): look for timezone offset, GPS time, etc. 206 timeZone := time.Local 207 if tz, _ := x.TimeZone(); tz != nil { 208 timeZone = tz 209 } 210 return time.ParseInLocation(exifTimeLayout, dateStr, timeZone) 211 } 212 213 type exifWalker struct { 214 x *_exif.Exif 215 vals map[string]any 216 includeMatcher *regexp.Regexp 217 excludeMatcher *regexp.Regexp 218 } 219 220 func (e *exifWalker) Walk(f _exif.FieldName, tag *tiff.Tag) error { 221 name := string(f) 222 if e.excludeMatcher != nil && e.excludeMatcher.MatchString(name) { 223 return nil 224 } 225 if e.includeMatcher != nil && !e.includeMatcher.MatchString(name) { 226 return nil 227 } 228 val, err := decodeTag(e.x, f, tag) 229 if err != nil { 230 return err 231 } 232 e.vals[name] = val 233 return nil 234 } 235 236 func nullString(in []byte) string { 237 var rv bytes.Buffer 238 for len(in) > 0 { 239 r, size := utf8.DecodeRune(in) 240 if unicode.IsGraphic(r) { 241 rv.WriteRune(r) 242 } 243 in = in[size:] 244 } 245 return rv.String() 246 } 247 248 var tcodec *tmc.Codec 249 250 func init() { 251 var err error 252 tcodec, err = tmc.New() 253 if err != nil { 254 panic(err) 255 } 256 } 257 258 // Tags is a map of EXIF tags. 259 type Tags map[string]any 260 261 // UnmarshalJSON is for internal use only. 262 func (v *Tags) UnmarshalJSON(b []byte) error { 263 vv := make(map[string]any) 264 if err := tcodec.Unmarshal(b, &vv); err != nil { 265 return err 266 } 267 268 *v = vv 269 270 return nil 271 } 272 273 // MarshalJSON is for internal use only. 274 func (v Tags) MarshalJSON() ([]byte, error) { 275 return tcodec.Marshal(v) 276 }