// 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: lstat: %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) ex, err := exif.Decode(r) if err != nil { return defaultTime() } ct, err = ex.DateTime() if err != nil { return defaultTime() } return ct, nil }
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 { log.Fatalf("exif.Decode: %v", err) } fmt.Printf("exif.Decode = %#v\n", ex) ct, err := ex.DateTime() fmt.Printf("exif.DateTime = %v, %v\n", ct, err) }
func main() { flag.Parse() fname := flag.Arg(0) f, err := os.Open(fname) if err != nil { log.Fatal(err) } x, err := exif.Decode(f) if err != nil { log.Fatal(err) } x.Walk(Walker{}) }
func ExampleDecode() { fname := "sample1.jpg" f, err := os.Open(fname) if err != nil { log.Fatal(err) } x, err := exif.Decode(f) if err != nil { log.Fatal(err) } camModel, _ := x.Get("Model") date, _ := x.Get("DateTimeOriginal") fmt.Println(camModel.StringVal()) fmt.Println(date.StringVal()) focal, _ := x.Get("FocalLength") numer, denom := focal.Rat2(0) // retrieve first (only) rat. value fmt.Printf("%v/%v", numer, denom) }
// TODO(mpl): move this test to the goexif lib if/when we contribute // back the DateTime stuff to upstream. func TestDateTime(t *testing.T) { f, err := os.Open(path.Join(datadir, "f1-exif.jpg")) if err != nil { t.Fatal(err) } defer f.Close() ex, err := exif.Decode(f) if err != nil { t.Fatal(err) } got, err := ex.DateTime() if err != nil { t.Fatal(err) } exifTimeLayout := "2006:01:02 15:04:05" want, err := time.Parse(exifTimeLayout, "2012:11:04 05:42:02") if err != nil { t.Fatal(err) } if got != want { t.Fatalf("Creation times differ; got %v, want: %v\n", got, want) } }
// 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) if err != nil { imageDebug("No valid EXIF.") } else { tag, err := ex.Get(exif.Orientation) if err != nil { imageDebug("No \"Orientation\" tag in EXIF.") } else { orient := tag.Int(0) switch orient { // those are the orientations that require // a rotation of ±90 case leftSideTop, rightSideTop, rightSideBottom, leftSideBottom: swapDimensions = true } } } 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 }
func indexEXIF(wholeRef blob.Ref, header []byte, mm *mutationMap) { ex, err := exif.Decode(bytes.NewReader(header)) if err != nil { return } 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 { log.Printf("Ignoring invalid EXIF file. Caught panic: %v", e) } }() 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.Ncomp) if tag.Format() == tiff.StringVal { numComp = 1 } var val bytes.Buffer val.WriteString(keyEXIFTag.Val(tagFmt, numComp, "")) if tag.Format() == tiff.StringVal { str := tag.StringVal() if containsUnsafeRawStrByte(str) { val.WriteString(urle(str)) } else { val.WriteString(str) } } else { for i := 0; i < int(tag.Ncomp); i++ { if i > 0 { val.WriteByte('|') } switch tagFmt { case "int": fmt.Fprintf(&val, "%d", tag.Int(i)) case "rat": n, d := tag.Rat2(i) fmt.Fprintf(&val, "%d/%d", n, d) case "float": fmt.Fprintf(&val, "%v", tag.Float(i)) default: panic("shouldn't get here") } } } valStr := val.String() mm.Set(key, valStr) return nil })) longTag, err := ex.Get(exif.FieldName("GPSLongitude")) if err != nil { return } ewTag, err := ex.Get(exif.FieldName("GPSLongitudeRef")) if err != nil { return } latTag, err := ex.Get(exif.FieldName("GPSLatitude")) if err != nil { return } nsTag, err := ex.Get(exif.FieldName("GPSLatitudeRef")) if err != nil { return } long := tagDegrees(longTag) lat := tagDegrees(latTag) if ewTag.StringVal() == "W" { long *= -1.0 } if nsTag.StringVal() == "S" { lat *= -1.0 } mm.Set(keyEXIFGPS.Key(wholeRef), keyEXIFGPS.Val(fmt.Sprint(lat), fmt.Sprint(long))) }
// Decode decodes an image from r using the provided decoding options. // The string returned is the format name returned by image.Decode. // If opts is nil, the defaults are used. func Decode(r io.Reader, opts *DecodeOpts) (image.Image, string, error) { var buf bytes.Buffer tr := io.TeeReader(io.LimitReader(r, 2<<20), &buf) angle := 0 flipMode := FlipDirection(0) if opts.useEXIF() { ex, err := exif.Decode(tr) if err != nil { imageDebug("No valid EXIF; will not rotate or flip.") return image.Decode(io.MultiReader(&buf, r)) } tag, err := ex.Get(exif.Orientation) if err != nil { imageDebug("No \"Orientation\" tag in EXIF; will not rotate or flip.") return image.Decode(io.MultiReader(&buf, r)) } orient := tag.Val[1] switch orient { case 1: // do nothing case 2: flipMode = 2 case 3: angle = 180 case 4: angle = 180 flipMode = 2 case 5: angle = -90 flipMode = 2 case 6: angle = -90 case 7: angle = 90 flipMode = 2 case 8: angle = 90 } } else { if opts.forcedRotate() { var ok bool angle, ok = opts.Rotate.(int) if !ok { return nil, "", fmt.Errorf("Rotate should be an int, not a %T", opts.Rotate) } } if opts.forcedFlip() { var ok bool flipMode, ok = opts.Flip.(FlipDirection) if !ok { return nil, "", fmt.Errorf("Flip should be a FlipDirection, not a %T", opts.Flip) } } } im, err := jpeg.Decode(io.MultiReader(&buf, r)) if err != nil { return nil, "", err } return flip(rotate(im, angle), flipMode), "jpeg", nil }
// Decode decodes an image from r using the provided decoding options. // The Config returned is similar to the one from the image package, // with the addition of the Modified field which indicates if the // image was actually flipped or rotated. // If opts is nil, the defaults are used. func Decode(r io.Reader, opts *DecodeOpts) (image.Image, Config, error) { var c Config var buf bytes.Buffer tr := io.TeeReader(io.LimitReader(r, 2<<20), &buf) angle := 0 flipMode := FlipDirection(0) if opts.useEXIF() { ex, err := exif.Decode(tr) maybeRescale := func() (image.Image, Config, error) { im, format, err := image.Decode(io.MultiReader(&buf, r)) if err == nil && opts.wantRescale(im.Bounds()) { im = rescale(im, opts) c.Modified = true } c.Format = format c.setBounds(im) return im, c, err } if err != nil { imageDebug("No valid EXIF; will not rotate or flip.") return maybeRescale() } tag, err := ex.Get(exif.Orientation) if err != nil { imageDebug("No \"Orientation\" tag in EXIF; will not rotate or flip.") return maybeRescale() } orient := tag.Int(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 } } else { if opts.forcedRotate() { var ok bool angle, ok = opts.Rotate.(int) if !ok { return nil, c, fmt.Errorf("Rotate should be an int, not a %T", opts.Rotate) } } if opts.forcedFlip() { var ok bool flipMode, ok = opts.Flip.(FlipDirection) if !ok { return nil, c, fmt.Errorf("Flip should be a FlipDirection, not a %T", opts.Flip) } } } im, format, err := image.Decode(io.MultiReader(&buf, r)) if err != nil { return nil, c, err } rescaled := false if opts.wantRescale(im.Bounds()) { im = rescale(im, opts) rescaled = true } im = flip(rotate(im, angle), flipMode) modified := true if angle == 0 && flipMode == 0 && !rescaled { modified = false } c.Format = format c.Modified = modified c.setBounds(im) return im, c, nil }
// Decode decodes an image from r using the provided decoding options. // The Config returned is similar to the one from the image package, // with the addition of the Modified field which indicates if the // image was actually flipped or rotated. // If opts is nil, the defaults are used. func Decode(r io.Reader, opts *DecodeOpts) (image.Image, Config, error) { var c Config var buf bytes.Buffer tr := io.TeeReader(io.LimitReader(r, 2<<20), &buf) angle := 0 flipMode := FlipDirection(0) if opts.useEXIF() { ex, err := exif.Decode(tr) if err != nil { imageDebug("No valid EXIF; will not rotate or flip.") im, format, err := image.Decode(io.MultiReader(&buf, r)) c.Format = format c.setBounds(im) return im, c, err } tag, err := ex.Get(exif.Orientation) if err != nil { imageDebug("No \"Orientation\" tag in EXIF; will not rotate or flip.") im, format, err := image.Decode(io.MultiReader(&buf, r)) c.Format = format c.setBounds(im) return im, c, err } orient := tag.Int(0) switch orient { case 1: // do nothing case 2: flipMode = 2 case 3: angle = 180 case 4: angle = 180 flipMode = 2 case 5: angle = -90 flipMode = 2 case 6: angle = -90 case 7: angle = 90 flipMode = 2 case 8: angle = 90 } } else { if opts.forcedRotate() { var ok bool angle, ok = opts.Rotate.(int) if !ok { return nil, c, fmt.Errorf("Rotate should be an int, not a %T", opts.Rotate) } } if opts.forcedFlip() { var ok bool flipMode, ok = opts.Flip.(FlipDirection) if !ok { return nil, c, fmt.Errorf("Flip should be a FlipDirection, not a %T", opts.Flip) } } } im, err := jpeg.Decode(io.MultiReader(&buf, r)) if err != nil { return nil, c, err } im = flip(rotate(im, angle), flipMode) modified := true if angle == 0 && flipMode == 0 { modified = false } c.Format = "jpeg" c.Modified = modified c.setBounds(im) return im, c, nil }