//best effort set location from exif data. func setLocationFromExif(l *core.LatLon, x *exif.Exif) { lat, err := x.Get(exif.GPSLatitude) if err != nil || lat.Format() != tiff.RatVal || lat.Count != 3 { return } lon, err := x.Get(exif.GPSLongitude) if err != nil || lon.Format() != tiff.RatVal || lon.Count != 3 { return } //we should have 3 rational values. and this lib panics on fail. //so we did the checks first. //OK it no longer panics, so we need to error check. so we add a helper rats := func(tag *tiff.Tag) []*big.Rat { r := make([]*big.Rat, 3) r[0], _ = tag.Rat(0) r[1], _ = tag.Rat(1) r[2], _ = tag.Rat(2) return r } l.Lat = toDecimalDegreesFromRat(rats(lat)) l.Lon = toDecimalDegreesFromRat(rats(lon)) //l is a pointer, so we have filled in the values now, // and do not need to return anything. }
func tagTime(ex *exif.Exif, ts exif.FieldName) (t time.Time, err error) { t = time.Unix(0, 0) tg, err := ex.Get(ts) if err != nil { return } str_val, err := tg.StringVal() if err != nil { return } t, err = time.Parse("2006:01:02 15:04:05", str_val) return }
// Returns pointer to struct GeoFields // can easily be accessed E.Lat, E.Long etc func GetGPS(E *exif.Exif) (*GeoFields, error) { F := new(GeoFields) LatVal, err := E.Get("GPSLatitude") if err != nil { return nil, err } F.Lat = FormatGPS(LatVal) LongVal, err := E.Get("GPSLongitude") if err != nil { return nil, err } F.Long = FormatGPS(LongVal) //Need to add more comments. Would help LatRefVal, err := E.Get("GPSLatitudeRef") //Lat and LatRef if err != nil { return nil, err } F.LatRef = LatRefVal.String() LongRefVal, err := E.Get("GPSLongitudeRef") if err != nil { return nil, err } F.LongRef = LongRefVal.String() // *** Longitude return F, nil }
// Parse the date string from an exif tag and return the year, month and day // in the format YYYYMMDD. func (i *ImageObj) getDatePath(tags *exif.Exif) (string, error) { value, err := tags.Get(exif.DateTimeOriginal) if err != nil { return "", err } dateStr, err := value.StringVal() if err != nil { return "", err } dateSlice := strings.SplitN(dateStr, ":", 3) return path.Join(dateSlice[0], dateSlice[1]), nil }
func ImageOrientation(ii *ImageInfo, x *exif.Exif) { orientation, err := x.Get(exif.Orientation) if err != nil { log.Println("o err: ", err) return } or := 0 if orientation.Val[0] != 0 { or = int(orientation.Val[0]) } if orientation.Val[1] != 0 { or = int(orientation.Val[1]) } ii.Orientation = or }
// This is basically a copy of the exif.Exif.DateTime() method, except: // * it takes a *time.Location to assume // * the caller already assumes there's no timezone offset or GPS time // in the EXIF, so any of that code can be ignored. func exifDateTimeInLocation(x *exif.Exif, loc *time.Location) (time.Time, error) { tag, err := x.Get(exif.DateTimeOriginal) if err != nil { tag, err = x.Get(exif.DateTime) if err != nil { return time.Time{}, err } } if tag.Format() != tiff.StringVal { return time.Time{}, errors.New("DateTime[Original] not in string format") } const exifTimeLayout = "2006:01:02 15:04:05" dateStr := strings.TrimRight(string(tag.Val), "\x00") return time.ParseInLocation(exifTimeLayout, dateStr, loc) }
func tagInt(ex *exif.Exif, ts exif.FieldName) (val int32, err error) { val = -1 tg, err := ex.Get(ts) if err != nil { return } if tg.Count != 1 { err = errors.New(fmt.Sprintf("Tag count not 1: %s", ts)) return } int_val, err := tg.Int(0) if err == nil { val = int32(int_val) } return }
// Parse decodes all Nikon makernote data found in x and adds it to x. func (_ *nikonV3) Parse(x *exif.Exif) error { m, err := x.Get(exif.MakerNote) if err != nil { return nil } else if bytes.Compare(m.Val[:6], []byte("Nikon\000")) != 0 { return nil } // Nikon v3 maker note is a self-contained IFD (offsets are relative // to the start of the maker note) mkNotes, err := tiff.Decode(bytes.NewReader(m.Val[10:])) if err != nil { return err } x.LoadTags(mkNotes.Dirs[0], makerNoteNikon3Fields, false) return nil }
func NewGeoPhotoFromExif(exifData *exif.Exif) GeoPhoto { GPSLatitude, _ := exifData.Get(exif.GPSLatitude) GPSLatitudeRef, _ := exifData.Get(exif.GPSLatitudeRef) GPSLongitude, _ := exifData.Get(exif.GPSLongitude) GPSLongitudeRef, _ := exifData.Get(exif.GPSLongitudeRef) GPSTimeStamp, _ := exifData.Get(exif.GPSTimeStamp) GPSDateStamp, _ := exifData.Get(exif.GPSDateStamp) return GeoPhoto{ GPSLatitude: GPSLatitude, GPSLatitudeRef: GPSLatitudeRef, GPSLongitude: GPSLongitude, GPSLongitudeRef: GPSLongitudeRef, GPSTimeStamp: GPSTimeStamp, GPSDateStamp: GPSDateStamp, } }
// Generate a thumbnail from the given image, save it to S3, and save the record to the DB. func SaveThumbnail( proc func(image.Image, int, int, imaging.ResampleFilter) *image.NRGBA, photoId int32, photoImage image.Image, ex *exif.Exif, width, height int32) { thumbnail := proc(photoImage, int(width), int(height), imaging.Lanczos) // If the EXIF says to, rotate the thumbnail. var orientation int = 1 if ex != nil { if orientationTag, err := ex.Get(exif.Orientation); err == nil { orientation = int(orientationTag.Int(0)) } if rotateFunc, ok := REORIENTATION_FUNCS[orientation]; ok && rotateFunc != nil { thumbnail = rotateFunc(thumbnail) } } var thumbnailBuffer bytes.Buffer err := jpeg.Encode(&thumbnailBuffer, thumbnail, nil) if err != nil { rev.ERROR.Println("Failed to create thumbnail:", err) return } thumbnailModel := &models.Thumbnail{ PhotoId: photoId, Width: width, Height: height, } err = PHOTO_BUCKET.PutReader(thumbnailModel.S3Path(), &thumbnailBuffer, int64(thumbnailBuffer.Len()), "image/jpeg", s3.PublicRead) if err != nil { rev.ERROR.Println("Failed to create thumbnail:", err) return } dbm.Insert(thumbnailModel) }
// Parse decodes all Canon makernote data found in x and adds it to x. func (_ *canon) Parse(x *exif.Exif) error { m, err := x.Get(exif.MakerNote) if err != nil { return nil } mk, err := x.Get(exif.Make) if err != nil { return nil } if val, err := mk.StringVal(); err != nil || val != "Canon" { return nil } // Canon notes are a single IFD directory with no header. // Reader offsets need to be w.r.t. the original tiff structure. buf := bytes.NewReader(append(make([]byte, m.ValOffset), m.Val...)) buf.Seek(int64(m.ValOffset), 0) mkNotesDir, _, err := tiff.DecodeDir(buf, x.Tiff.Order) if err != nil { return err } x.LoadTags(mkNotesDir, makerNoteCanonFields, false) return nil }
func (info *ExifInfo) Decode(x *exif.Exif) { // normally, don't ignore errors! camModel, _ := x.Get(exif.Model) info.CameraModel, _ = camModel.StringVal() // retrieve first (only) rat. value focal, _ := x.Get(exif.FocalLength) numer, _, _ := focal.Rat2(0) //fmt.Printf("\nFocal : %v/%v", numer, denom) info.Focal = numer // retrieve first (only) rat. value aperture, _ := x.Get(exif.FNumber) numer, _, _ = aperture.Rat2(0) //fmt.Printf("\nAperture : %v/%v", numer, denom) info.Aperture = numer iso, _ := x.Get(exif.ISOSpeedRatings) //fmt.Printf("\n%v", iso) info.ISO = iso.String() }
func storeImage(rw http.ResponseWriter, req *http.Request) { // Appengine var c appengine.Context // Google Cloud Storage authentication var cc gcscontext.Context // Google Cloud Storage bucket name var bucketName string = "" // Google Cloud Storage client var client *storage.Client // Google Cloud Storage bucket var bucketHandle *storage.BucketHandle // User uploaded image file name var fileName string = uuid.New() // Transform user uploaded image to a thumbnail file name var fileNameThumbnail string = uuid.New() // User uploaded image file type var contentType string = "" // User uploaded image file raw data var b []byte // Google Cloud Storage file writer var wc *storage.Writer = nil // Error var err error = nil // Result, 0: success, 1: failed var r int = http.StatusCreated // Set response in the end defer func() { // Return status. WriteHeader() must be called before call to Write if r == http.StatusCreated { // Changing the header after a call to WriteHeader (or Write) has no effect. // rw.Header().Set("Location", req.URL.String()+"/"+cKey.Encode()) rw.Header().Set("Location", "http://"+bucketName+".storage.googleapis.com/"+fileName) rw.Header().Set("X-Thumbnail", "http://"+bucketName+".storage.googleapis.com/"+fileNameThumbnail) rw.WriteHeader(r) } else { http.Error(rw, http.StatusText(r), r) } }() // To log information in Google APP Engine console c = appengine.NewContext(req) // Get data from body b, err = ioutil.ReadAll(req.Body) if err != nil { c.Errorf("%s in reading body", err) r = http.StatusInternalServerError return } c.Infof("Body length %d bytes, read %d bytes", req.ContentLength, len(b)) // Determine filename extension from content type contentType = req.Header["Content-Type"][0] switch contentType { case "image/jpeg": fileName += ".jpg" fileNameThumbnail += ".jpg" default: c.Errorf("Unknown or unsupported content type '%s'. Valid: image/jpeg", contentType) r = http.StatusBadRequest return } c.Infof("Content type %s is received, %s is detected.", contentType, http.DetectContentType(b)) // Prepare Google Cloud Storage authentication cc = gcsappengine.NewContext(req) if client, err = storage.NewClient(cc); err != nil { c.Errorf("%s in initializing a GCS client", err) r = http.StatusInternalServerError return } defer client.Close() // Get default bucket if bucketName, err = gcsfile.DefaultBucketName(cc); err != nil { c.Errorf("%s in getting default GCS bucket name", err) r = http.StatusInternalServerError return } bucketHandle = client.Bucket(bucketName) c.Infof("APP Engine Version: %s", gcsappengine.VersionID(cc)) c.Infof("Using bucket name: %s", bucketName) // Change default object ACLs if err = bucketHandle.DefaultObjectACL().Set(cc, storage.AllUsers, storage.RoleReader); err != nil { c.Errorf("%v in saving default object ACL rule for bucket %q", err, bucketName) r = http.StatusInternalServerError return } // Store rotated image in Google Cloud Storage var in *bytes.Reader = bytes.NewReader(b) var x *exif.Exif = nil var orientation *tiff.Tag = nil var beforeImage image.Image var afterImage *image.NRGBA = nil // Read EXIF if _, err = in.Seek(0, 0); err != nil { c.Errorf("%s in moving the reader offset to the beginning in order to read EXIF", err) return } if x, err = exif.Decode(in); err != nil { c.Errorf("%s in decoding JPEG image", err) return } // Get Orientation if orientation, err = x.Get(exif.Orientation); err != nil { c.Warningf("%s in getting orientation from EXIF", err) return } c.Debugf("Orientation %s", orientation.String()) // Open image if _, err = in.Seek(0, 0); err != nil { c.Errorf("%s in moving the reader offset to the beginning in order to read EXIF", err) return } if beforeImage, err = imaging.Decode(in); err != nil { c.Errorf("%s in opening image %s", err) return } switch orientation.String() { case "1": afterImage = beforeImage.(*image.NRGBA) case "2": afterImage = imaging.FlipH(beforeImage) case "3": afterImage = imaging.Rotate180(beforeImage) case "4": afterImage = imaging.FlipV(beforeImage) case "5": afterImage = imaging.Transverse(beforeImage) case "6": afterImage = imaging.Rotate270(beforeImage) case "7": afterImage = imaging.Transpose(beforeImage) case "8": afterImage = imaging.Rotate90(beforeImage) } // Save rotated image wc = bucketHandle.Object(fileName).NewWriter(cc) wc.ContentType = contentType if err = imaging.Encode(wc, afterImage, imaging.JPEG); err != nil { c.Errorf("%s in saving rotated image", err) return } if err = wc.Close(); err != nil { c.Errorf("CreateFile: unable to close bucket %q, file %q: %v", bucketName, fileName, err) r = 1 return } wc = nil // Make thumbnail if afterImage.Rect.Dx() > afterImage.Rect.Dy() { afterImage = imaging.Resize(afterImage, 1920, 0, imaging.Lanczos) } else { afterImage = imaging.Resize(afterImage, 0, 1920, imaging.Lanczos) } // Save thumbnail wc = bucketHandle.Object(fileNameThumbnail).NewWriter(cc) wc.ContentType = contentType if imaging.Encode(wc, afterImage, imaging.JPEG); err != nil { c.Errorf("%s in saving image thumbnail", err) return } if err = wc.Close(); err != nil { c.Errorf("CreateFileThumbnail: unable to close bucket %q, file %q: %v", bucketName, fileNameThumbnail, err) r = 1 return } c.Infof("/%v/%v, /%v/%v created", bucketName, fileName, bucketName, fileNameThumbnail) }