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  }