func ExampleDecode() { fname := "sample1.jpg" f, err := os.Open(fname) if err != nil { log.Fatal(err) } // Optionally register camera makenote data parsing - currently Nikon and // Canon are supported. exif.RegisterParsers(mknote.All...) x, err := exif.Decode(f) if err != nil { log.Fatal(err) } camModel, _ := x.Get(exif.Model) // normally, don't ignore errors! fmt.Println(camModel.StringVal()) focal, _ := x.Get(exif.FocalLength) numer, denom, _ := focal.Rat2(0) // retrieve first (only) rat. value fmt.Printf("%v/%v", numer, denom) // Two convenience functions exist for date/time taken and GPS coords: tm, _ := x.DateTime() fmt.Println("Taken: ", tm) lat, long, _ := x.LatLong() fmt.Println("lat, long: ", lat, ", ", long) }
// 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 }
// 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 }
// exifOrientation parses the EXIF data in r and returns the stored // orientation as the angle and flip necessary to transform the image. func exifOrientation(r io.Reader) (int, FlipDirection) { var ( angle int flipMode FlipDirection ) ex, err := exif.Decode(r) if err != nil { imageDebug("No valid EXIF; will not rotate or flip.") return 0, 0 } tag, err := ex.Get(exif.Orientation) if err != nil { imageDebug(`No "Orientation" tag in EXIF; will not rotate or flip.`) return 0, 0 } orient, err := tag.Int(0) if err != nil { imageDebug(fmt.Sprintf("EXIF error: %v", err)) return 0, 0 } switch orient { case topLeftSide: // do nothing case topRightSide: flipMode = 2 case bottomRightSide: angle = 180 case bottomLeftSide: angle = 180 flipMode = 2 case leftSideTop: angle = -90 flipMode = 2 case rightSideTop: angle = -90 case rightSideBottom: angle = 90 flipMode = 2 case leftSideBottom: angle = 90 } return angle, flipMode }
func showEXIF(file string) { f, err := os.Open(file) if err != nil { panic(err.Error()) } defer f.Close() ex, err := exif.Decode(f) if err != nil { if exif.IsCriticalError(err) { log.Fatalf("exif.Decode, critical error: %v", err) } log.Printf("exif.Decode, warning: %v", err) } fmt.Printf("%v\n", ex) if exif.IsExifError(err) { // the error happened while decoding the EXIF sub-IFD, so as DateTime is // part of it, we have to assume (until there's a better "decode effort" // strategy in goexif) that it's not usable. return } ct, err := ex.DateTime() fmt.Printf("exif.DateTime = %v, %v\n", ct, err) }
func main() { flag.Parse() fnames := flag.Args() if *mnote { exif.RegisterParsers(mknote.All...) } for _, name := range fnames { f, err := os.Open(name) if err != nil { log.Printf("err on %v: %v", name, err) continue } x, err := exif.Decode(f) if err != nil { log.Printf("err on %v: %v", name, err) continue } if *thumb { data, err := x.JpegThumbnail() if err != nil { log.Fatal("no thumbnail present") } if _, err := os.Stdout.Write(data); err != nil { log.Fatal(err) } return } fmt.Printf("\n---- Image '%v' ----\n", name) x.Walk(Walker{}) } }
func indexEXIF(wholeRef blob.Ref, reader io.Reader, mm *mutationMap) (err error) { var tiffErr error ex, err := exif.Decode(reader) if err != nil { tiffErr = err if exif.IsCriticalError(err) { 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 { mm.Set(keyEXIFGPS.Key(wholeRef), keyEXIFGPS.Val(fmt.Sprint(lat), fmt.Sprint(long))) } else if !exif.IsTagNotPresentError(err) { log.Printf("Invalid EXIF GPS data: %v", err) } return nil }