1
Fork 0

Add exif parsing

This commit is contained in:
viktorstrate 2020-02-24 23:30:08 +01:00
parent 890e36b7a8
commit b10c607f3a
12 changed files with 453 additions and 115 deletions

View File

@ -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)
);

View File

@ -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
)

View File

@ -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=

View File

@ -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:

View File

@ -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
}

View File

@ -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"`

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

248
api/scanner/exif.go Normal file
View File

@ -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")
}

View File

@ -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
}

View File

@ -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