mirror of https://github.com/perkeep/perkeep.git
schema: use GPS location to find timezone from EXIF when UTC offset is unknown
Change-Id: Ia3590424db36508b491d8f19829738fe102e5c3d
This commit is contained in:
parent
286b53f119
commit
077763fcbb
|
@ -30,6 +30,7 @@ import (
|
|||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
@ -40,7 +41,9 @@ import (
|
|||
|
||||
"camlistore.org/pkg/blob"
|
||||
"camlistore.org/pkg/types"
|
||||
"camlistore.org/third_party/github.com/bradfitz/latlong"
|
||||
"camlistore.org/third_party/github.com/camlistore/goexif/exif"
|
||||
"camlistore.org/third_party/github.com/camlistore/goexif/tiff"
|
||||
)
|
||||
|
||||
// MaxSchemaBlobSize represents the upper bound for how large
|
||||
|
@ -905,5 +908,69 @@ func FileTime(f io.ReaderAt) (time.Time, error) {
|
|||
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 lat, long, ok := ex.LatLong(); ok {
|
||||
if loc := lookupLocation(latlong.LookupZoneName(lat, long)); loc != nil {
|
||||
if t, err := exifDateTimeInLocation(ex, loc); err == nil {
|
||||
return t, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ct, nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
var zoneCache struct {
|
||||
sync.RWMutex
|
||||
m map[string]*time.Location
|
||||
}
|
||||
|
||||
func lookupLocation(zone string) *time.Location {
|
||||
if zone == "" {
|
||||
return nil
|
||||
}
|
||||
zoneCache.RLock()
|
||||
l, ok := zoneCache.m[zone]
|
||||
zoneCache.RUnlock()
|
||||
if ok {
|
||||
return l
|
||||
}
|
||||
// could use singleflight here, but doesn't really
|
||||
// matter if two callers both do this.
|
||||
loc, err := time.LoadLocation(zone)
|
||||
|
||||
zoneCache.Lock()
|
||||
if zoneCache.m == nil {
|
||||
zoneCache.m = make(map[string]*time.Location)
|
||||
}
|
||||
zoneCache.m[zone] = loc // even if nil
|
||||
zoneCache.Unlock()
|
||||
|
||||
if err != nil {
|
||||
log.Printf("failed to lookup timezone %q: %v", zone, err)
|
||||
return nil
|
||||
}
|
||||
return loc
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ package schema
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
@ -609,3 +610,34 @@ func TestStaticSocket(t *testing.T) {
|
|||
t.Fatalf("StaticFile.AsStaticSocket(): Expected true, got false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimezoneEXIFCorrection(t *testing.T) {
|
||||
// Test that we get UTC times for photos taken in two
|
||||
// different timezones.
|
||||
// Both only have local time + GPS in the exif.
|
||||
tests := []struct {
|
||||
file, want, wantUTC string
|
||||
}{
|
||||
{"coffee-sf.jpg", "2014-07-11 08:44:34 -0700 PDT", "2014-07-11 15:44:34 +0000 UTC"},
|
||||
{"gocon-tokyo.jpg", "2014-05-31 13:34:04 +0900 JST", "2014-05-31 04:34:04 +0000 UTC"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
f, err := os.Open("testdata/" + tt.file)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Hide *os.File type from FileTime, so it can't use modtime:
|
||||
tm, err := FileTime(struct{ io.ReaderAt }{f})
|
||||
f.Close()
|
||||
if err != nil {
|
||||
t.Errorf("%s: %v", tt.file, err)
|
||||
continue
|
||||
}
|
||||
if got := tm.String(); got != tt.want {
|
||||
t.Errorf("%s: time = %q; want %q", tt.file, got, tt.want)
|
||||
}
|
||||
if got := tm.UTC().String(); got != tt.wantUTC {
|
||||
t.Errorf("%s: utc time = %q; want %q", tt.file, got, tt.wantUTC)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 27 KiB |
Binary file not shown.
After Width: | Height: | Size: 27 KiB |
Loading…
Reference in New Issue