Exemple #1
0
// FileTime returns the best guess of the file's creation time (or modtime).
// If the file doesn't have its own metadata indication the creation time (such as in EXIF),
// FileTime uses the modification time from the file system.
// It there was a valid EXIF but an error while trying to get a date from it,
// it logs the error and tries the other methods.
func FileTime(f io.ReaderAt) (time.Time, error) {
	var ct time.Time
	defaultTime := func() (time.Time, error) {
		if osf, ok := f.(*os.File); ok {
			fi, err := osf.Stat()
			if err != nil {
				return ct, fmt.Errorf("Failed to find a modtime: stat: %v", err)
			}
			return fi.ModTime(), nil
		}
		return ct, errors.New("All methods failed to find a creation time or modtime.")
	}

	size, ok := findSize(f)
	if !ok {
		size = 256 << 10 // enough to get the EXIF
	}
	r := io.NewSectionReader(f, 0, size)
	var tiffErr error
	ex, err := exif.Decode(r)
	if err != nil {
		tiffErr = err
		if exif.IsShortReadTagValueError(err) {
			return ct, io.ErrUnexpectedEOF
		}
		if exif.IsCriticalError(err) || exif.IsExifError(err) {
			return defaultTime()
		}
	}
	ct, err = ex.DateTime()
	if err != nil {
		return defaultTime()
	}
	// If the EXIF file only had local timezone, but it did have
	// GPS, then lookup the timezone and correct the time.
	if ct.Location() == time.Local {
		if exif.IsGPSError(tiffErr) {
			log.Printf("Invalid EXIF GPS data: %v", tiffErr)
			return ct, nil
		}
		if lat, long, err := ex.LatLong(); err == nil {
			if loc := lookupLocation(latlong.LookupZoneName(lat, long)); loc != nil {
				if t, err := exifDateTimeInLocation(ex, loc); err == nil {
					return t, nil
				}
			}
		} else if !exif.IsTagNotPresentError(err) {
			log.Printf("Invalid EXIF GPS data: %v", err)
		}
	}
	return ct, nil
}
Exemple #2
0
// DecodeConfig returns the image Config similarly to
// the standard library's image.DecodeConfig with the
// addition that it also checks for an EXIF orientation,
// and sets the Width and Height as they would visibly
// be after correcting for that orientation.
func DecodeConfig(r io.Reader) (Config, error) {
	var c Config
	var buf bytes.Buffer
	tr := io.TeeReader(io.LimitReader(r, 2<<20), &buf)
	swapDimensions := false

	ex, err := exif.Decode(tr)
	// trigger a retry when there isn't enough data for reading exif data from a tiff file
	if exif.IsShortReadTagValueError(err) {
		return c, io.ErrUnexpectedEOF
	}
	if err != nil {
		imageDebug(fmt.Sprintf("No valid EXIF, error: %v.", err))
	} else {
		tag, err := ex.Get(exif.Orientation)
		if err != nil {
			imageDebug(`No "Orientation" tag in EXIF.`)
		} else {
			orient, err := tag.Int(0)
			if err == nil {
				switch orient {
				// those are the orientations that require
				// a rotation of ±90
				case leftSideTop, rightSideTop, rightSideBottom, leftSideBottom:
					swapDimensions = true
				}
			} else {
				imageDebug(fmt.Sprintf("EXIF Error: %v", err))
			}
		}
	}
	conf, format, err := image.DecodeConfig(io.MultiReader(&buf, r))
	if err != nil {
		imageDebug(fmt.Sprintf("Image Decoding failed: %v", err))
		return c, err
	}
	c.Format = format
	if swapDimensions {
		c.Width, c.Height = conf.Height, conf.Width
	} else {
		c.Width, c.Height = conf.Width, conf.Height
	}
	return c, err
}
Exemple #3
0
func indexEXIF(wholeRef blob.Ref, r io.Reader, mm *mutationMap) (err error) {
	var tiffErr error
	ex, err := exif.Decode(r)
	if err != nil {
		tiffErr = err
		if exif.IsCriticalError(err) {
			if exif.IsShortReadTagValueError(err) {
				return io.ErrUnexpectedEOF // trigger a retry with whole file
			}
			return
		}
		log.Printf("Non critical TIFF decoding error: %v", err)
	}
	defer func() {
		// The EXIF library panics if you access a field past
		// what the file contains.  Be paranoid and just
		// recover here, instead of crashing on an invalid
		// EXIF file.
		if e := recover(); e != nil {
			err = errEXIFPanic
		}
	}()

	err = ex.Walk(exifWalkFunc(func(name exif.FieldName, tag *tiff.Tag) error {
		tagFmt := tagFormatString(tag)
		if tagFmt == "" {
			return nil
		}
		key := keyEXIFTag.Key(wholeRef, fmt.Sprintf("%04x", tag.Id))
		numComp := int(tag.Count)
		if tag.Format() == tiff.StringVal {
			numComp = 1
		}
		var val bytes.Buffer
		val.WriteString(keyEXIFTag.Val(tagFmt, numComp, ""))
		if tag.Format() == tiff.StringVal {
			str, err := tag.StringVal()
			if err != nil {
				log.Printf("Invalid EXIF string data: %v", err)
				return nil
			}
			if containsUnsafeRawStrByte(str) {
				val.WriteString(urle(str))
			} else {
				val.WriteString(str)
			}
		} else {
			for i := 0; i < int(tag.Count); i++ {
				if i > 0 {
					val.WriteByte('|')
				}
				switch tagFmt {
				case "int":
					v, err := tag.Int(i)
					if err != nil {
						log.Printf("Invalid EXIF int data: %v", err)
						return nil
					}
					fmt.Fprintf(&val, "%d", v)
				case "rat":
					n, d, err := tag.Rat2(i)
					if err != nil {
						log.Printf("Invalid EXIF rat data: %v", err)
						return nil
					}
					fmt.Fprintf(&val, "%d/%d", n, d)
				case "float":
					v, err := tag.Float(i)
					if err != nil {
						log.Printf("Invalid EXIF float data: %v", err)
						return nil
					}
					fmt.Fprintf(&val, "%v", v)
				default:
					panic("shouldn't get here")
				}
			}
		}
		valStr := val.String()
		mm.Set(key, valStr)
		return nil
	}))
	if err != nil {
		return
	}

	if exif.IsGPSError(tiffErr) {
		log.Printf("Invalid EXIF GPS data: %v", tiffErr)
		return nil
	}
	if lat, long, err := ex.LatLong(); err == nil {
		if math.Abs(long) > 180.0 || math.Abs(lat) > 90.0 {
			log.Printf("Long, lat outside allowed range: %v, %v", long, lat)
			return nil
		}
		// index 7 places fixed precision (~10mm worst case at equator)
		// http://stackoverflow.com/a/1947615/114581
		mm.Set(keyEXIFGPS.Key(wholeRef), keyEXIFGPS.Val(fmt.Sprintf("%.7f", lat), fmt.Sprintf("%.7f", long)))
	} else if !exif.IsTagNotPresentError(err) {
		log.Printf("Invalid EXIF GPS data: %v", err)
	}
	return nil
}