github.com/kovansky/hugo@v0.92.3-0.20220224232819-63076e4ff19f/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 type Exif struct { 36 Lat float64 37 Long float64 38 Date time.Time 39 Tags Tags 40 } 41 42 type Decoder struct { 43 includeFieldsRe *regexp.Regexp 44 excludeFieldsrRe *regexp.Regexp 45 noDate bool 46 noLatLong bool 47 } 48 49 func IncludeFields(expression string) func(*Decoder) error { 50 return func(d *Decoder) error { 51 re, err := compileRegexp(expression) 52 if err != nil { 53 return err 54 } 55 d.includeFieldsRe = re 56 return nil 57 } 58 } 59 60 func ExcludeFields(expression string) func(*Decoder) error { 61 return func(d *Decoder) error { 62 re, err := compileRegexp(expression) 63 if err != nil { 64 return err 65 } 66 d.excludeFieldsrRe = re 67 return nil 68 } 69 } 70 71 func WithLatLongDisabled(disabled bool) func(*Decoder) error { 72 return func(d *Decoder) error { 73 d.noLatLong = disabled 74 return nil 75 } 76 } 77 78 func WithDateDisabled(disabled bool) func(*Decoder) error { 79 return func(d *Decoder) error { 80 d.noDate = disabled 81 return nil 82 } 83 } 84 85 func compileRegexp(expression string) (*regexp.Regexp, error) { 86 expression = strings.TrimSpace(expression) 87 if expression == "" { 88 return nil, nil 89 } 90 if !strings.HasPrefix(expression, "(") { 91 // Make it case insensitive 92 expression = "(?i)" + expression 93 } 94 95 return regexp.Compile(expression) 96 } 97 98 func NewDecoder(options ...func(*Decoder) error) (*Decoder, error) { 99 d := &Decoder{} 100 for _, opt := range options { 101 if err := opt(d); err != nil { 102 return nil, err 103 } 104 } 105 106 return d, nil 107 } 108 109 func (d *Decoder) Decode(r io.Reader) (ex *Exif, err error) { 110 defer func() { 111 if r := recover(); r != nil { 112 err = fmt.Errorf("Exif failed: %v", r) 113 } 114 }() 115 116 var x *_exif.Exif 117 x, err = _exif.Decode(r) 118 if err != nil { 119 if err.Error() == "EOF" { 120 // Found no Exif 121 return nil, nil 122 } 123 return 124 } 125 126 var tm time.Time 127 var lat, long float64 128 129 if !d.noDate { 130 tm, _ = x.DateTime() 131 } 132 133 if !d.noLatLong { 134 lat, long, _ = x.LatLong() 135 } 136 137 walker := &exifWalker{x: x, vals: make(map[string]interface{}), includeMatcher: d.includeFieldsRe, excludeMatcher: d.excludeFieldsrRe} 138 if err = x.Walk(walker); err != nil { 139 return 140 } 141 142 ex = &Exif{Lat: lat, Long: long, Date: tm, Tags: walker.vals} 143 144 return 145 } 146 147 func decodeTag(x *_exif.Exif, f _exif.FieldName, t *tiff.Tag) (interface{}, error) { 148 switch t.Format() { 149 case tiff.StringVal, tiff.UndefVal: 150 s := nullString(t.Val) 151 if strings.Contains(string(f), "DateTime") { 152 if d, err := tryParseDate(x, s); err == nil { 153 return d, nil 154 } 155 } 156 return s, nil 157 case tiff.OtherVal: 158 return "unknown", nil 159 } 160 161 var rv []interface{} 162 163 for i := 0; i < int(t.Count); i++ { 164 switch t.Format() { 165 case tiff.RatVal: 166 n, d, _ := t.Rat2(i) 167 rat := big.NewRat(n, d) 168 if n == 1 { 169 rv = append(rv, rat) 170 } else { 171 f, _ := rat.Float64() 172 rv = append(rv, f) 173 } 174 175 case tiff.FloatVal: 176 v, _ := t.Float(i) 177 rv = append(rv, v) 178 case tiff.IntVal: 179 v, _ := t.Int(i) 180 rv = append(rv, v) 181 } 182 } 183 184 if t.Count == 1 { 185 if len(rv) == 1 { 186 return rv[0], nil 187 } 188 } 189 190 return rv, nil 191 } 192 193 // Code borrowed from exif.DateTime and adjusted. 194 func tryParseDate(x *_exif.Exif, s string) (time.Time, error) { 195 dateStr := strings.TrimRight(s, "\x00") 196 // TODO(bep): look for timezone offset, GPS time, etc. 197 timeZone := time.Local 198 if tz, _ := x.TimeZone(); tz != nil { 199 timeZone = tz 200 } 201 return time.ParseInLocation(exifTimeLayout, dateStr, timeZone) 202 } 203 204 type exifWalker struct { 205 x *_exif.Exif 206 vals map[string]interface{} 207 includeMatcher *regexp.Regexp 208 excludeMatcher *regexp.Regexp 209 } 210 211 func (e *exifWalker) Walk(f _exif.FieldName, tag *tiff.Tag) error { 212 name := string(f) 213 if e.excludeMatcher != nil && e.excludeMatcher.MatchString(name) { 214 return nil 215 } 216 if e.includeMatcher != nil && !e.includeMatcher.MatchString(name) { 217 return nil 218 } 219 val, err := decodeTag(e.x, f, tag) 220 if err != nil { 221 return err 222 } 223 e.vals[name] = val 224 return nil 225 } 226 227 func nullString(in []byte) string { 228 var rv bytes.Buffer 229 for len(in) > 0 { 230 r, size := utf8.DecodeRune(in) 231 if unicode.IsGraphic(r) { 232 rv.WriteRune(r) 233 } 234 in = in[size:] 235 } 236 return rv.String() 237 } 238 239 var tcodec *tmc.Codec 240 241 func init() { 242 var err error 243 tcodec, err = tmc.New() 244 if err != nil { 245 panic(err) 246 } 247 } 248 249 type Tags map[string]interface{} 250 251 func (v *Tags) UnmarshalJSON(b []byte) error { 252 vv := make(map[string]interface{}) 253 if err := tcodec.Unmarshal(b, &vv); err != nil { 254 return err 255 } 256 257 *v = vv 258 259 return nil 260 } 261 262 func (v Tags) MarshalJSON() ([]byte, error) { 263 return tcodec.Marshal(v) 264 }