hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

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 }