diff --git a/api/database/migrations/0002_photo.up.sql b/api/database/migrations/0002_photo.up.sql
index 15c8dcb..5d27a98 100644
--- a/api/database/migrations/0002_photo.up.sql
+++ b/api/database/migrations/0002_photo.up.sql
@@ -3,13 +3,14 @@ CREATE TABLE IF NOT EXISTS photo_exif (
   camera varchar(256),
   maker varchar(256),
   lens varchar(256),
-  dateShot timestamp,
-  file_size_bytes bigint,
+  dateShot timestamp NULL,
   exposure varchar(256),
-  aperature float,
+  aperture float,
   iso int(6),
   focal_length float,
   flash varchar(256),
+  orientation int(1),
+  exposure_program int(1),
 
   PRIMARY KEY (exif_id)
 );
diff --git a/api/go.mod b/api/go.mod
index 8b73d49..695f711 100644
--- a/api/go.mod
+++ b/api/go.mod
@@ -5,8 +5,6 @@ go 1.13
 require (
 	github.com/99designs/gqlgen v0.10.2
 	github.com/fatih/color v1.9.0
-	github.com/go-chi/chi v3.3.2+incompatible
-	github.com/go-chi/cors v1.0.0
 	github.com/go-sql-driver/mysql v1.5.0
 	github.com/golang-migrate/migrate v3.5.4+incompatible
 	github.com/gorilla/mux v1.7.4
@@ -16,10 +14,9 @@ require (
 	github.com/lib/pq v1.3.0
 	github.com/nf/cr2 v0.0.0-20180623103828-4699471a17ed
 	github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
-	github.com/rs/cors v1.6.0
-	github.com/sirupsen/logrus v1.4.2
 	github.com/vektah/gqlparser v1.2.0
 	github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0
+	github.com/xor-gate/goexif2 v1.1.0
 	golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
 	golang.org/x/image v0.0.0-20200119044424-58c23975cae1
 )
diff --git a/api/go.sum b/api/go.sum
index 03aa30f..a767839 100644
--- a/api/go.sum
+++ b/api/go.sum
@@ -6,17 +6,27 @@ github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dsoprea/go-exif v0.0.0-20200126052615-bd04addaf40f h1:yIPu74TXwq1dxn9edEn4p1si4DW/c/h7sgEJ/zMZNcg=
+github.com/dsoprea/go-exif v0.0.0-20200126052615-bd04addaf40f/go.mod h1:lOaOt7+UEppOgyvRy749v3do836U/hw0YVJNjoyPaEs=
+github.com/dsoprea/go-exif/v2 v2.0.0-20200126052615-bd04addaf40f h1:LJ4lH4r8MgKEC5HeSNRw81lfVOlAM0xELOOvf712H0o=
+github.com/dsoprea/go-exif/v2 v2.0.0-20200126052615-bd04addaf40f/go.mod h1:Lm2lMM2zx8p4a34ZemkaUV95AnMl4ZvLbCUbwOvLC2E=
+github.com/dsoprea/go-logging v0.0.0-20190624164917-c4f10aab7696 h1:VGFnZAcLwPpt1sHlAxml+pGLZz9A2s+K/s1YNhPC91Y=
+github.com/dsoprea/go-logging v0.0.0-20190624164917-c4f10aab7696/go.mod h1:Nm/x2ZUNRW6Fe5C3LxdY1PyZY5wmDv/s5dkPJ/VB3iA=
 github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
 github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
 github.com/go-chi/chi v3.3.2+incompatible h1:uQNcQN3NsV1j4ANsPh42P4ew4t6rnRbJb8frvpp31qQ=
 github.com/go-chi/chi v3.3.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
 github.com/go-chi/cors v1.0.0 h1:e6x8k7uWbUwYs+aXDoiUzeQFT6l0cygBYyNhD7/1Tg0=
 github.com/go-chi/cors v1.0.0/go.mod h1:K2Yje0VW/SJzxiyMYu6iPQYa7hMjQX2i/F491VChg1I=
+github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
+github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
 github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
 github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/golang-migrate/migrate v3.5.4+incompatible h1:R7OzwvCJTCgwapPCiX6DyBiu2czIUMDCB118gFTKTUA=
 github.com/golang-migrate/migrate v3.5.4+incompatible/go.mod h1:IsVUlFN5puWOmXrqjgGUfIRIbU7mr8oNBE2tyERd9Wk=
+github.com/golang/geo v0.0.0-20190916061304-5b978397cfec h1:lJwO/92dFXWeXOZdoGXgptLmNLwynMSHUmU6besqtiw=
+github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
 github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
 github.com/gorilla/mux v1.6.1 h1:KOwqsTYZdeuMacU7CxjMNYEKeBvLbxW+psodrbcEa3A=
 github.com/gorilla/mux v1.6.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
@@ -76,11 +86,15 @@ github.com/vektah/gqlparser v1.2.0/go.mod h1:bkVf0FX+Stjg/MHnm8mEyubuaArhNEqfQhF
 github.com/viktorstrate/photoview v0.0.0-20200119220544-691e4c7dc433 h1:n6jGnDctC9HI7B1rnc5ATQPYaaxQaJtS8bI6oI0QV34=
 github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0 h1:3UeQBvD0TFrlVjOeLOBz+CPAI8dnbqNSVwUwRrkp7vQ=
 github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0/go.mod h1:IXCdmsXIht47RaVFLEdVnh1t+pgYtTAhQGj73kz+2DM=
+github.com/xor-gate/goexif2 v1.1.0 h1:OvTZ5iEvsDhRWFjV5xY3wT7uHFna28nSSP7ucau+cXQ=
+github.com/xor-gate/goexif2 v1.1.0/go.mod h1:eRjn3VSkAwpNpxEx/CGmd0zg0JFGL3akrSMxnJ581AY=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/image v0.0.0-20200119044424-58c23975cae1 h1:5h3ngYt7+vXCDZCup/HkCQgW5XwmSvR/nA2JmJ0RErg=
 golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8=
+golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -96,5 +110,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
+gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 sourcegraph.com/sourcegraph/appdash v0.0.0-20180110180208-2cc67fd64755/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
 sourcegraph.com/sourcegraph/appdash-data v0.0.0-20151005221446-73f23eafcf67/go.mod h1:L5q+DGLGOQFpo1snNEkLOJT2d1YTW66rWNzatr3He1k=
diff --git a/api/gqlgen.yml b/api/gqlgen.yml
index 6726748..d022285 100644
--- a/api/gqlgen.yml
+++ b/api/gqlgen.yml
@@ -20,12 +20,16 @@ resolver:
 autobind: []
 
 models:
+  ID:
+    model: github.com/99designs/gqlgen/graphql.IntID
   User:
     model: github.com/viktorstrate/photoview/api/graphql/models.User
   Photo:
     model: github.com/viktorstrate/photoview/api/graphql/models.Photo
   PhotoURL:
     model: github.com/viktorstrate/photoview/api/graphql/models.PhotoURL
+  PhotoEXIF:
+    model: github.com/viktorstrate/photoview/api/graphql/models.PhotoEXIF
   Album:
     model: github.com/viktorstrate/photoview/api/graphql/models.Album
   ShareToken:
diff --git a/api/graphql/generated.go b/api/graphql/generated.go
index f2de422..4bfb0c2 100644
--- a/api/graphql/generated.go
+++ b/api/graphql/generated.go
@@ -113,17 +113,18 @@ type ComplexityRoot struct {
 	}
 
 	PhotoExif struct {
-		Aperture    func(childComplexity int) int
-		Camera      func(childComplexity int) int
-		DateShot    func(childComplexity int) int
-		Exposure    func(childComplexity int) int
-		FileSize    func(childComplexity int) int
-		Flash       func(childComplexity int) int
-		FocalLength func(childComplexity int) int
-		Iso         func(childComplexity int) int
-		Lens        func(childComplexity int) int
-		Maker       func(childComplexity int) int
-		Photo       func(childComplexity int) int
+		Aperture        func(childComplexity int) int
+		Camera          func(childComplexity int) int
+		DateShot        func(childComplexity int) int
+		Exposure        func(childComplexity int) int
+		ExposureProgram func(childComplexity int) int
+		Flash           func(childComplexity int) int
+		FocalLength     func(childComplexity int) int
+		ID              func(childComplexity int) int
+		Iso             func(childComplexity int) int
+		Lens            func(childComplexity int) int
+		Maker           func(childComplexity int) int
+		Photo           func(childComplexity int) int
 	}
 
 	PhotoURL struct {
@@ -201,7 +202,7 @@ type PhotoResolver interface {
 	Thumbnail(ctx context.Context, obj *models.Photo) (*models.PhotoURL, error)
 	HighRes(ctx context.Context, obj *models.Photo) (*models.PhotoURL, error)
 	Album(ctx context.Context, obj *models.Photo) (*models.Album, error)
-	Exif(ctx context.Context, obj *models.Photo) (*models.PhotoExif, error)
+	Exif(ctx context.Context, obj *models.Photo) (*models.PhotoEXIF, error)
 	Shares(ctx context.Context, obj *models.Photo) ([]*models.ShareToken, error)
 	Downloads(ctx context.Context, obj *models.Photo) ([]*models.PhotoDownload, error)
 }
@@ -629,12 +630,12 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
 
 		return e.complexity.PhotoExif.Exposure(childComplexity), true
 
-	case "PhotoEXIF.fileSize":
-		if e.complexity.PhotoExif.FileSize == nil {
+	case "PhotoEXIF.exposureProgram":
+		if e.complexity.PhotoExif.ExposureProgram == nil {
 			break
 		}
 
-		return e.complexity.PhotoExif.FileSize(childComplexity), true
+		return e.complexity.PhotoExif.ExposureProgram(childComplexity), true
 
 	case "PhotoEXIF.flash":
 		if e.complexity.PhotoExif.Flash == nil {
@@ -650,6 +651,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
 
 		return e.complexity.PhotoExif.FocalLength(childComplexity), true
 
+	case "PhotoEXIF.id":
+		if e.complexity.PhotoExif.ID == nil {
+			break
+		}
+
+		return e.complexity.PhotoExif.ID(childComplexity), true
+
 	case "PhotoEXIF.iso":
 		if e.complexity.PhotoExif.Iso == nil {
 			break
@@ -1192,6 +1200,7 @@ type Photo {
 
 "EXIF metadata from the camera"
 type PhotoEXIF {
+  id: Int!
   photo: Photo
   "The model name of the camera"
   camera: String
@@ -1200,8 +1209,6 @@ type PhotoEXIF {
   "The name of the lens"
   lens: String
   dateShot: Time
-  "The formatted filesize of the image"
-  fileSize: String
   "The exposure time of the image"
   exposure: String
   "The aperature stops of the image"
@@ -1209,9 +1216,11 @@ type PhotoEXIF {
   "The ISO setting of the image"
   iso: Int
   "The focal length of the lens, when the image was taken"
-  focalLength: String
+  focalLength: Float
   "A formatted description of the flash settings, when the image was taken"
   flash: String
+  "An index describing the mode for adjusting the exposure of the image"
+  exposureProgram: Int
 }
 `},
 )
@@ -3162,10 +3171,10 @@ func (ec *executionContext) _Photo_exif(ctx context.Context, field graphql.Colle
 	if resTmp == nil {
 		return graphql.Null
 	}
-	res := resTmp.(*models.PhotoExif)
+	res := resTmp.(*models.PhotoEXIF)
 	rctx.Result = res
 	ctx = ec.Tracer.StartFieldChildExecution(ctx)
-	return ec.marshalOPhotoEXIF2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐPhotoExif(ctx, field.Selections, res)
+	return ec.marshalOPhotoEXIF2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐPhotoEXIF(ctx, field.Selections, res)
 }
 
 func (ec *executionContext) _Photo_shares(ctx context.Context, field graphql.CollectedField, obj *models.Photo) (ret graphql.Marshaler) {
@@ -3390,7 +3399,7 @@ func (ec *executionContext) _PhotoDownload_url(ctx context.Context, field graphq
 	return ec.marshalNString2string(ctx, field.Selections, res)
 }
 
-func (ec *executionContext) _PhotoEXIF_photo(ctx context.Context, field graphql.CollectedField, obj *models.PhotoExif) (ret graphql.Marshaler) {
+func (ec *executionContext) _PhotoEXIF_id(ctx context.Context, field graphql.CollectedField, obj *models.PhotoEXIF) (ret graphql.Marshaler) {
 	ctx = ec.Tracer.StartFieldExecution(ctx, field)
 	defer func() {
 		if r := recover(); r != nil {
@@ -3403,13 +3412,50 @@ func (ec *executionContext) _PhotoEXIF_photo(ctx context.Context, field graphql.
 		Object:   "PhotoEXIF",
 		Field:    field,
 		Args:     nil,
-		IsMethod: false,
+		IsMethod: true,
 	}
 	ctx = graphql.WithResolverContext(ctx, rctx)
 	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
 	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
 		ctx = rctx // use context from middleware stack in children
-		return obj.Photo, nil
+		return obj.ID(), nil
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		if !ec.HasError(rctx) {
+			ec.Errorf(ctx, "must not be null")
+		}
+		return graphql.Null
+	}
+	res := resTmp.(int)
+	rctx.Result = res
+	ctx = ec.Tracer.StartFieldChildExecution(ctx)
+	return ec.marshalNInt2int(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) _PhotoEXIF_photo(ctx context.Context, field graphql.CollectedField, obj *models.PhotoEXIF) (ret graphql.Marshaler) {
+	ctx = ec.Tracer.StartFieldExecution(ctx, field)
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+		ec.Tracer.EndFieldExecution(ctx)
+	}()
+	rctx := &graphql.ResolverContext{
+		Object:   "PhotoEXIF",
+		Field:    field,
+		Args:     nil,
+		IsMethod: true,
+	}
+	ctx = graphql.WithResolverContext(ctx, rctx)
+	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return obj.Photo(), nil
 	})
 	if err != nil {
 		ec.Error(ctx, err)
@@ -3424,7 +3470,7 @@ func (ec *executionContext) _PhotoEXIF_photo(ctx context.Context, field graphql.
 	return ec.marshalOPhoto2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐPhoto(ctx, field.Selections, res)
 }
 
-func (ec *executionContext) _PhotoEXIF_camera(ctx context.Context, field graphql.CollectedField, obj *models.PhotoExif) (ret graphql.Marshaler) {
+func (ec *executionContext) _PhotoEXIF_camera(ctx context.Context, field graphql.CollectedField, obj *models.PhotoEXIF) (ret graphql.Marshaler) {
 	ctx = ec.Tracer.StartFieldExecution(ctx, field)
 	defer func() {
 		if r := recover(); r != nil {
@@ -3458,7 +3504,7 @@ func (ec *executionContext) _PhotoEXIF_camera(ctx context.Context, field graphql
 	return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
 }
 
-func (ec *executionContext) _PhotoEXIF_maker(ctx context.Context, field graphql.CollectedField, obj *models.PhotoExif) (ret graphql.Marshaler) {
+func (ec *executionContext) _PhotoEXIF_maker(ctx context.Context, field graphql.CollectedField, obj *models.PhotoEXIF) (ret graphql.Marshaler) {
 	ctx = ec.Tracer.StartFieldExecution(ctx, field)
 	defer func() {
 		if r := recover(); r != nil {
@@ -3492,7 +3538,7 @@ func (ec *executionContext) _PhotoEXIF_maker(ctx context.Context, field graphql.
 	return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
 }
 
-func (ec *executionContext) _PhotoEXIF_lens(ctx context.Context, field graphql.CollectedField, obj *models.PhotoExif) (ret graphql.Marshaler) {
+func (ec *executionContext) _PhotoEXIF_lens(ctx context.Context, field graphql.CollectedField, obj *models.PhotoEXIF) (ret graphql.Marshaler) {
 	ctx = ec.Tracer.StartFieldExecution(ctx, field)
 	defer func() {
 		if r := recover(); r != nil {
@@ -3526,7 +3572,7 @@ func (ec *executionContext) _PhotoEXIF_lens(ctx context.Context, field graphql.C
 	return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
 }
 
-func (ec *executionContext) _PhotoEXIF_dateShot(ctx context.Context, field graphql.CollectedField, obj *models.PhotoExif) (ret graphql.Marshaler) {
+func (ec *executionContext) _PhotoEXIF_dateShot(ctx context.Context, field graphql.CollectedField, obj *models.PhotoEXIF) (ret graphql.Marshaler) {
 	ctx = ec.Tracer.StartFieldExecution(ctx, field)
 	defer func() {
 		if r := recover(); r != nil {
@@ -3560,41 +3606,7 @@ func (ec *executionContext) _PhotoEXIF_dateShot(ctx context.Context, field graph
 	return ec.marshalOTime2ᚖtimeᚐTime(ctx, field.Selections, res)
 }
 
-func (ec *executionContext) _PhotoEXIF_fileSize(ctx context.Context, field graphql.CollectedField, obj *models.PhotoExif) (ret graphql.Marshaler) {
-	ctx = ec.Tracer.StartFieldExecution(ctx, field)
-	defer func() {
-		if r := recover(); r != nil {
-			ec.Error(ctx, ec.Recover(ctx, r))
-			ret = graphql.Null
-		}
-		ec.Tracer.EndFieldExecution(ctx)
-	}()
-	rctx := &graphql.ResolverContext{
-		Object:   "PhotoEXIF",
-		Field:    field,
-		Args:     nil,
-		IsMethod: false,
-	}
-	ctx = graphql.WithResolverContext(ctx, rctx)
-	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
-	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
-		ctx = rctx // use context from middleware stack in children
-		return obj.FileSize, nil
-	})
-	if err != nil {
-		ec.Error(ctx, err)
-		return graphql.Null
-	}
-	if resTmp == nil {
-		return graphql.Null
-	}
-	res := resTmp.(*string)
-	rctx.Result = res
-	ctx = ec.Tracer.StartFieldChildExecution(ctx)
-	return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
-}
-
-func (ec *executionContext) _PhotoEXIF_exposure(ctx context.Context, field graphql.CollectedField, obj *models.PhotoExif) (ret graphql.Marshaler) {
+func (ec *executionContext) _PhotoEXIF_exposure(ctx context.Context, field graphql.CollectedField, obj *models.PhotoEXIF) (ret graphql.Marshaler) {
 	ctx = ec.Tracer.StartFieldExecution(ctx, field)
 	defer func() {
 		if r := recover(); r != nil {
@@ -3628,7 +3640,7 @@ func (ec *executionContext) _PhotoEXIF_exposure(ctx context.Context, field graph
 	return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
 }
 
-func (ec *executionContext) _PhotoEXIF_aperture(ctx context.Context, field graphql.CollectedField, obj *models.PhotoExif) (ret graphql.Marshaler) {
+func (ec *executionContext) _PhotoEXIF_aperture(ctx context.Context, field graphql.CollectedField, obj *models.PhotoEXIF) (ret graphql.Marshaler) {
 	ctx = ec.Tracer.StartFieldExecution(ctx, field)
 	defer func() {
 		if r := recover(); r != nil {
@@ -3662,7 +3674,7 @@ func (ec *executionContext) _PhotoEXIF_aperture(ctx context.Context, field graph
 	return ec.marshalOFloat2ᚖfloat64(ctx, field.Selections, res)
 }
 
-func (ec *executionContext) _PhotoEXIF_iso(ctx context.Context, field graphql.CollectedField, obj *models.PhotoExif) (ret graphql.Marshaler) {
+func (ec *executionContext) _PhotoEXIF_iso(ctx context.Context, field graphql.CollectedField, obj *models.PhotoEXIF) (ret graphql.Marshaler) {
 	ctx = ec.Tracer.StartFieldExecution(ctx, field)
 	defer func() {
 		if r := recover(); r != nil {
@@ -3696,7 +3708,7 @@ func (ec *executionContext) _PhotoEXIF_iso(ctx context.Context, field graphql.Co
 	return ec.marshalOInt2ᚖint(ctx, field.Selections, res)
 }
 
-func (ec *executionContext) _PhotoEXIF_focalLength(ctx context.Context, field graphql.CollectedField, obj *models.PhotoExif) (ret graphql.Marshaler) {
+func (ec *executionContext) _PhotoEXIF_focalLength(ctx context.Context, field graphql.CollectedField, obj *models.PhotoEXIF) (ret graphql.Marshaler) {
 	ctx = ec.Tracer.StartFieldExecution(ctx, field)
 	defer func() {
 		if r := recover(); r != nil {
@@ -3724,13 +3736,13 @@ func (ec *executionContext) _PhotoEXIF_focalLength(ctx context.Context, field gr
 	if resTmp == nil {
 		return graphql.Null
 	}
-	res := resTmp.(*string)
+	res := resTmp.(*float64)
 	rctx.Result = res
 	ctx = ec.Tracer.StartFieldChildExecution(ctx)
-	return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
+	return ec.marshalOFloat2ᚖfloat64(ctx, field.Selections, res)
 }
 
-func (ec *executionContext) _PhotoEXIF_flash(ctx context.Context, field graphql.CollectedField, obj *models.PhotoExif) (ret graphql.Marshaler) {
+func (ec *executionContext) _PhotoEXIF_flash(ctx context.Context, field graphql.CollectedField, obj *models.PhotoEXIF) (ret graphql.Marshaler) {
 	ctx = ec.Tracer.StartFieldExecution(ctx, field)
 	defer func() {
 		if r := recover(); r != nil {
@@ -3764,6 +3776,40 @@ func (ec *executionContext) _PhotoEXIF_flash(ctx context.Context, field graphql.
 	return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
 }
 
+func (ec *executionContext) _PhotoEXIF_exposureProgram(ctx context.Context, field graphql.CollectedField, obj *models.PhotoEXIF) (ret graphql.Marshaler) {
+	ctx = ec.Tracer.StartFieldExecution(ctx, field)
+	defer func() {
+		if r := recover(); r != nil {
+			ec.Error(ctx, ec.Recover(ctx, r))
+			ret = graphql.Null
+		}
+		ec.Tracer.EndFieldExecution(ctx)
+	}()
+	rctx := &graphql.ResolverContext{
+		Object:   "PhotoEXIF",
+		Field:    field,
+		Args:     nil,
+		IsMethod: false,
+	}
+	ctx = graphql.WithResolverContext(ctx, rctx)
+	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return obj.ExposureProgram, nil
+	})
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.(*int)
+	rctx.Result = res
+	ctx = ec.Tracer.StartFieldChildExecution(ctx)
+	return ec.marshalOInt2ᚖint(ctx, field.Selections, res)
+}
+
 func (ec *executionContext) _PhotoURL_url(ctx context.Context, field graphql.CollectedField, obj *models.PhotoURL) (ret graphql.Marshaler) {
 	ctx = ec.Tracer.StartFieldExecution(ctx, field)
 	defer func() {
@@ -6531,7 +6577,7 @@ func (ec *executionContext) _PhotoDownload(ctx context.Context, sel ast.Selectio
 
 var photoEXIFImplementors = []string{"PhotoEXIF"}
 
-func (ec *executionContext) _PhotoEXIF(ctx context.Context, sel ast.SelectionSet, obj *models.PhotoExif) graphql.Marshaler {
+func (ec *executionContext) _PhotoEXIF(ctx context.Context, sel ast.SelectionSet, obj *models.PhotoEXIF) graphql.Marshaler {
 	fields := graphql.CollectFields(ec.RequestContext, sel, photoEXIFImplementors)
 
 	out := graphql.NewFieldSet(fields)
@@ -6540,6 +6586,11 @@ func (ec *executionContext) _PhotoEXIF(ctx context.Context, sel ast.SelectionSet
 		switch field.Name {
 		case "__typename":
 			out.Values[i] = graphql.MarshalString("PhotoEXIF")
+		case "id":
+			out.Values[i] = ec._PhotoEXIF_id(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				invalids++
+			}
 		case "photo":
 			out.Values[i] = ec._PhotoEXIF_photo(ctx, field, obj)
 		case "camera":
@@ -6550,8 +6601,6 @@ func (ec *executionContext) _PhotoEXIF(ctx context.Context, sel ast.SelectionSet
 			out.Values[i] = ec._PhotoEXIF_lens(ctx, field, obj)
 		case "dateShot":
 			out.Values[i] = ec._PhotoEXIF_dateShot(ctx, field, obj)
-		case "fileSize":
-			out.Values[i] = ec._PhotoEXIF_fileSize(ctx, field, obj)
 		case "exposure":
 			out.Values[i] = ec._PhotoEXIF_exposure(ctx, field, obj)
 		case "aperture":
@@ -6562,6 +6611,8 @@ func (ec *executionContext) _PhotoEXIF(ctx context.Context, sel ast.SelectionSet
 			out.Values[i] = ec._PhotoEXIF_focalLength(ctx, field, obj)
 		case "flash":
 			out.Values[i] = ec._PhotoEXIF_flash(ctx, field, obj)
+		case "exposureProgram":
+			out.Values[i] = ec._PhotoEXIF_exposureProgram(ctx, field, obj)
 		default:
 			panic("unknown field " + strconv.Quote(field.Name))
 		}
@@ -7932,11 +7983,11 @@ func (ec *executionContext) marshalOPhoto2ᚖgithubᚗcomᚋviktorstrateᚋphoto
 	return ec._Photo(ctx, sel, v)
 }
 
-func (ec *executionContext) marshalOPhotoEXIF2githubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐPhotoExif(ctx context.Context, sel ast.SelectionSet, v models.PhotoExif) graphql.Marshaler {
+func (ec *executionContext) marshalOPhotoEXIF2githubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐPhotoEXIF(ctx context.Context, sel ast.SelectionSet, v models.PhotoEXIF) graphql.Marshaler {
 	return ec._PhotoEXIF(ctx, sel, &v)
 }
 
-func (ec *executionContext) marshalOPhotoEXIF2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐPhotoExif(ctx context.Context, sel ast.SelectionSet, v *models.PhotoExif) graphql.Marshaler {
+func (ec *executionContext) marshalOPhotoEXIF2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐPhotoEXIF(ctx context.Context, sel ast.SelectionSet, v *models.PhotoEXIF) graphql.Marshaler {
 	if v == nil {
 		return graphql.Null
 	}
diff --git a/api/graphql/models/generated.go b/api/graphql/models/generated.go
index 2f54781..c806d17 100644
--- a/api/graphql/models/generated.go
+++ b/api/graphql/models/generated.go
@@ -6,7 +6,6 @@ import (
 	"fmt"
 	"io"
 	"strconv"
-	"time"
 )
 
 type AuthorizeResult struct {
@@ -39,30 +38,6 @@ type PhotoDownload struct {
 	URL    string `json:"url"`
 }
 
-// EXIF metadata from the camera
-type PhotoExif struct {
-	Photo *Photo `json:"photo"`
-	// The model name of the camera
-	Camera *string `json:"camera"`
-	// The maker of the camera
-	Maker *string `json:"maker"`
-	// The name of the lens
-	Lens     *string    `json:"lens"`
-	DateShot *time.Time `json:"dateShot"`
-	// The formatted filesize of the image
-	FileSize *string `json:"fileSize"`
-	// The exposure time of the image
-	Exposure *string `json:"exposure"`
-	// The aperature stops of the image
-	Aperture *float64 `json:"aperture"`
-	// The ISO setting of the image
-	Iso *int `json:"iso"`
-	// The focal length of the lens, when the image was taken
-	FocalLength *string `json:"focalLength"`
-	// A formatted description of the flash settings, when the image was taken
-	Flash *string `json:"flash"`
-}
-
 type ScannerResult struct {
 	Finished bool     `json:"finished"`
 	Success  bool     `json:"success"`
diff --git a/api/graphql/models/photo_exif.go b/api/graphql/models/photo_exif.go
new file mode 100644
index 0000000..bd40ffd
--- /dev/null
+++ b/api/graphql/models/photo_exif.go
@@ -0,0 +1,39 @@
+package models
+
+import (
+	"database/sql"
+	"time"
+)
+
+type PhotoEXIF struct {
+	ExifID          int
+	Camera          *string
+	Maker           *string
+	Lens            *string
+	DateShot        *time.Time
+	Exposure        *string
+	Aperture        *float64
+	Iso             *int
+	FocalLength     *float64
+	Flash           *string
+	Orientation     *int
+	ExposureProgram *int
+}
+
+func (exif *PhotoEXIF) Photo() *Photo {
+	panic("not implemented")
+}
+
+func (exif *PhotoEXIF) ID() int {
+	return exif.ExifID
+}
+
+func NewPhotoExifFromRow(row *sql.Row) (*PhotoEXIF, error) {
+	exif := PhotoEXIF{}
+
+	if err := row.Scan(&exif.ExifID, &exif.Camera, &exif.Maker, &exif.Lens, &exif.DateShot, &exif.Exposure, &exif.Aperture, &exif.Iso, &exif.FocalLength, &exif.Flash, &exif.Orientation, &exif.ExposureProgram); err != nil {
+		return nil, err
+	}
+
+	return &exif, nil
+}
diff --git a/api/graphql/resolvers/photo.go b/api/graphql/resolvers/photo.go
index 3123870..73ef268 100644
--- a/api/graphql/resolvers/photo.go
+++ b/api/graphql/resolvers/photo.go
@@ -146,7 +146,7 @@ func (r *photoResolver) Album(ctx context.Context, obj *models.Photo) (*models.A
 	panic("not implemented")
 }
 
-func (r *photoResolver) Exif(ctx context.Context, obj *models.Photo) (*models.PhotoExif, error) {
-	log.Println("Photo: EXIF not implemented")
-	return nil, nil
+func (r *photoResolver) Exif(ctx context.Context, obj *models.Photo) (*models.PhotoEXIF, error) {
+	row := r.Database.QueryRow("SELECT photo_exif.* FROM photo NATURAL JOIN photo_exif WHERE photo.photo_id = ?", obj.PhotoID)
+	return models.NewPhotoExifFromRow(row)
 }
diff --git a/api/graphql/schema.graphql b/api/graphql/schema.graphql
index e3b88f4..f73236c 100644
--- a/api/graphql/schema.graphql
+++ b/api/graphql/schema.graphql
@@ -202,6 +202,7 @@ type Photo {
 
 "EXIF metadata from the camera"
 type PhotoEXIF {
+  id: Int!
   photo: Photo
   "The model name of the camera"
   camera: String
@@ -210,8 +211,6 @@ type PhotoEXIF {
   "The name of the lens"
   lens: String
   dateShot: Time
-  "The formatted filesize of the image"
-  fileSize: String
   "The exposure time of the image"
   exposure: String
   "The aperature stops of the image"
@@ -219,7 +218,9 @@ type PhotoEXIF {
   "The ISO setting of the image"
   iso: Int
   "The focal length of the lens, when the image was taken"
-  focalLength: String
+  focalLength: Float
   "A formatted description of the flash settings, when the image was taken"
   flash: String
+  "An index describing the mode for adjusting the exposure of the image"
+  exposureProgram: Int
 }
diff --git a/api/scanner/exif.go b/api/scanner/exif.go
new file mode 100644
index 0000000..9e85275
--- /dev/null
+++ b/api/scanner/exif.go
@@ -0,0 +1,248 @@
+package scanner
+
+import (
+	"database/sql"
+	"errors"
+	"log"
+	"math/big"
+	"os"
+
+	"github.com/viktorstrate/photoview/api/graphql/models"
+	"github.com/xor-gate/goexif2/exif"
+	"github.com/xor-gate/goexif2/mknote"
+)
+
+func ScanEXIF(tx *sql.Tx, photo *models.Photo) (*models.PhotoEXIF, error) {
+
+	log.Printf("Scanning for EXIF")
+
+	{
+		// Check if EXIF data already exists
+		if photo.ExifId != nil {
+			row := tx.QueryRow("SELECT * FROM photo_exif WHERE exif_id = ?", photo.ExifId)
+			return models.NewPhotoExifFromRow(row)
+		}
+
+		row := tx.QueryRow("SELECT photo_exif.* FROM photo, photo_exif WHERE photo.exif_id = photo_exif.exif_id AND photo.photo_id = ?", photo.PhotoID)
+		exifData, err := models.NewPhotoExifFromRow(row)
+		if err != nil && err != sql.ErrNoRows {
+			return nil, err
+		} else if exifData != nil {
+			return exifData, nil
+		}
+	}
+
+	photoFile, err := os.Open(photo.Path)
+	if err != nil {
+		return nil, err
+	}
+
+	exif.RegisterParsers(mknote.All...)
+
+	exifTags, err := exif.Decode(photoFile)
+	if err != nil {
+		return nil, err
+	}
+
+	// log.Printf("EXIF DATA FOR %s\n%s\n", photo.Title, exifTags.String())
+
+	valueNames := make([]string, 0)
+	exifValues := make([]interface{}, 0)
+
+	model, err := readStringTag(exifTags, exif.Model, photo)
+	if err == nil {
+		valueNames = append(valueNames, "camera")
+		exifValues = append(exifValues, model)
+	}
+
+	maker, err := readStringTag(exifTags, exif.Make, photo)
+	if err == nil {
+		valueNames = append(valueNames, "maker")
+		exifValues = append(exifValues, maker)
+	}
+
+	lens, err := readStringTag(exifTags, exif.LensModel, photo)
+	if err == nil {
+		valueNames = append(valueNames, "lens")
+		exifValues = append(exifValues, lens)
+	}
+
+	date, err := exifTags.DateTime()
+	if err == nil {
+		valueNames = append(valueNames, "dateShot")
+		exifValues = append(exifValues, date)
+	}
+
+	exposure, err := readRationalTag(exifTags, exif.ExposureTime, photo)
+	if err == nil {
+		valueNames = append(valueNames, "exposure")
+		exifValues = append(exifValues, exposure.RatString())
+	}
+
+	apertureRat, err := readRationalTag(exifTags, exif.FNumber, photo)
+	if err == nil {
+		aperture, _ := apertureRat.Float32()
+		valueNames = append(valueNames, "aperture")
+		exifValues = append(exifValues, aperture)
+	}
+
+	isoTag, err := exifTags.Get(exif.ISOSpeedRatings)
+	if err != nil {
+		log.Printf("WARN: Could not read ISOSpeedRatings from EXIF: %s\n", photo.Title)
+	} else {
+		iso, err := isoTag.Int(0)
+		if err != nil {
+			log.Printf("WARN: Could not parse EXIF ISOSpeedRatings as integer: %s\n", photo.Title)
+		} else {
+			valueNames = append(valueNames, "iso")
+			exifValues = append(exifValues, iso)
+		}
+	}
+
+	focalLengthRat, err := readRationalTag(exifTags, exif.FocalLength, photo)
+	if err == nil {
+		focalLength, _ := focalLengthRat.Float32()
+		valueNames = append(valueNames, "focal_length")
+		exifValues = append(exifValues, focalLength)
+	} else {
+		// For some photos, the focal length cannot be read as a rational value,
+		// but is instead the second value read as an integer
+		tag, err := exifTags.Get(exif.FocalLength)
+		if err == nil {
+			focalLength, err := tag.Int(1)
+			if err != nil {
+				log.Printf("WARN: Could not parse EXIF FocalLength as integer: %s\n%s\n", photo.Title, err)
+			} else {
+				valueNames = append(valueNames, "focal_length")
+				exifValues = append(exifValues, focalLength)
+			}
+		}
+	}
+
+	flash, err := exifTags.Flash()
+	if err == nil {
+		valueNames = append(valueNames, "flash")
+		exifValues = append(exifValues, flash)
+	}
+
+	orientation, err := readIntegerTag(exifTags, exif.Orientation, photo)
+	if err == nil {
+		valueNames = append(valueNames, "orientation")
+		exifValues = append(exifValues, *orientation)
+	}
+
+	exposureProgram, err := readIntegerTag(exifTags, exif.ExposureProgram, photo)
+	if err == nil {
+		valueNames = append(valueNames, "exposure_program")
+		exifValues = append(exifValues, *exposureProgram)
+	}
+
+	if len(valueNames) == 0 {
+		return nil, nil
+	}
+
+	prepareQuestions := ""
+	for range valueNames {
+		prepareQuestions += "?,"
+	}
+	prepareQuestions = prepareQuestions[0 : len(prepareQuestions)-1]
+
+	columns := ""
+	for _, name := range valueNames {
+		columns += name + ","
+	}
+	columns = columns[0 : len(columns)-1]
+
+	// Insert into database
+	result, err := tx.Exec("INSERT INTO photo_exif ("+columns+") VALUES ("+prepareQuestions+")", exifValues...)
+	if err != nil {
+		return nil, err
+	}
+
+	exifID, err := result.LastInsertId()
+	if err != nil {
+		return nil, err
+	}
+
+	// Link exif to photo in database
+	result, err = tx.Exec("UPDATE photo SET exif_id = ? WHERE photo_id = ?", exifID, photo.PhotoID)
+	if err != nil {
+		return nil, err
+	}
+
+	rowsAffected, err := result.RowsAffected()
+	if err != nil {
+		return nil, err
+	}
+
+	if rowsAffected == 0 {
+		return nil, errors.New("Linking exif to photo in database failed: 0 rows affected")
+	}
+
+	// Return newly created exif row
+	row := tx.QueryRow("SELECT * FROM photo_exif WHERE exif_id = ?", exifID)
+	return models.NewPhotoExifFromRow(row)
+}
+
+func readStringTag(tags *exif.Exif, name exif.FieldName, photo *models.Photo) (*string, error) {
+	tag, err := tags.Get(name)
+	if err != nil {
+		log.Printf("WARN: Could not read %s from EXIF: %s\n", name, photo.Title)
+		return nil, err
+	}
+
+	if tag != nil {
+		value, err := tag.StringVal()
+		if err != nil {
+			log.Printf("WARN: Could not parse %s from EXIF as string: %s\n", name, photo.Title)
+			return nil, err
+		}
+
+		return &value, nil
+	}
+
+	log.Printf("WARN: EXIF tag %s returned null: %s\n", name, photo.Title)
+	return nil, errors.New("exif tag returned null")
+}
+
+func readRationalTag(tags *exif.Exif, name exif.FieldName, photo *models.Photo) (*big.Rat, error) {
+	tag, err := tags.Get(name)
+	if err != nil {
+		log.Printf("WARN: Could not read %s from EXIF: %s\n", name, photo.Title)
+		return nil, err
+	}
+
+	if tag != nil {
+		value, err := tag.Rat(0)
+		if err != nil {
+			log.Printf("WARN: Could not parse %s from EXIF as rational: %s\n%s\n", name, photo.Title, err)
+			return nil, err
+		}
+
+		return value, nil
+	}
+
+	log.Printf("WARN: EXIF tag %s returned null: %s\n", name, photo.Title)
+	return nil, errors.New("exif tag returned null")
+}
+
+func readIntegerTag(tags *exif.Exif, name exif.FieldName, photo *models.Photo) (*int, error) {
+	tag, err := tags.Get(name)
+	if err != nil {
+		log.Printf("WARN: Could not read %s from EXIF: %s\n", name, photo.Title)
+		return nil, err
+	}
+
+	if tag != nil {
+		value, err := tag.Int(0)
+		if err != nil {
+			log.Printf("WARN: Could not parse %s from EXIF as integer: %s\n%s\n", name, photo.Title, err)
+			return nil, err
+		}
+
+		return &value, nil
+	}
+
+	log.Printf("WARN: EXIF tag %s returned null: %s\n", name, photo.Title)
+	return nil, errors.New("exif tag returned null")
+}
diff --git a/api/scanner/photo_scanner.go b/api/scanner/photo_scanner.go
index 078f007..27d7260 100644
--- a/api/scanner/photo_scanner.go
+++ b/api/scanner/photo_scanner.go
@@ -41,6 +41,12 @@ func ScanPhoto(tx *sql.Tx, photoPath string, albumId int, content_type *string)
 		return err
 	}
 
+	_, err = ScanEXIF(tx, photo)
+	if err != nil {
+		log.Printf("ERROR: ScanEXIF for %s: %s\n", photoName, err)
+		return err
+	}
+
 	if err := ProcessPhoto(tx, photo, content_type); err != nil {
 		return err
 	}
diff --git a/api/scanner/process_photo.go b/api/scanner/process_photo.go
index fb7acd2..6139928 100644
--- a/api/scanner/process_photo.go
+++ b/api/scanner/process_photo.go
@@ -49,7 +49,7 @@ func ProcessPhoto(tx *sql.Tx, photo *models.Photo, content_type *string) error {
 
 	log.Printf("Processing photo: %s\n", photo.Path)
 
-	imageData := processImageData{
+	imageData := ProcessImageData{
 		photoPath: photo.Path,
 	}
 
@@ -249,13 +249,13 @@ func encodeImageJPEG(photoPath string, photoImage image.Image, jpegOptions *jpeg
 	return nil
 }
 
-type processImageData struct {
+type ProcessImageData struct {
 	photoPath       string
 	_photoImage     image.Image
 	_thumbnailImage image.Image
 }
 
-func (img *processImageData) PhotoImage() (image.Image, error) {
+func (img *ProcessImageData) PhotoImage() (image.Image, error) {
 	if img._photoImage != nil {
 		return img._photoImage, nil
 	}
@@ -276,7 +276,7 @@ func (img *processImageData) PhotoImage() (image.Image, error) {
 	return img._photoImage, nil
 }
 
-func (img *processImageData) ThumbnailImage() (image.Image, error) {
+func (img *ProcessImageData) ThumbnailImage() (image.Image, error) {
 	photoImage, err := img.PhotoImage()
 	if err != nil {
 		return nil, err
