From d81e945b68336f0976a60decdccc27136164b03d Mon Sep 17 00:00:00 2001 From: viktorstrate Date: Sun, 2 Feb 2020 00:29:42 +0100 Subject: [PATCH] Start on image proccessing --- api/database/migrations/0002_photo.down.sql | 1 + api/database/migrations/0002_photo.up.sql | 28 +- api/graphql/generated.go | 614 +++++++++++++++++++- api/graphql/models/generated.go | 31 +- api/graphql/models/user.go | 14 +- api/graphql/schema.graphql | 26 +- api/scanner/process_image.go | 15 + api/scanner/scanner.go | 7 +- 8 files changed, 720 insertions(+), 16 deletions(-) create mode 100644 api/scanner/process_image.go diff --git a/api/database/migrations/0002_photo.down.sql b/api/database/migrations/0002_photo.down.sql index 4c8187bd..e3995cc6 100644 --- a/api/database/migrations/0002_photo.down.sql +++ b/api/database/migrations/0002_photo.down.sql @@ -1,3 +1,4 @@ DROP TABLE IF EXISTS photo; DROP TABLE IF EXISTS album; DROP TABLE IF EXISTS photo_url; +DROP TABLE IF EXISTS photo_exif; diff --git a/api/database/migrations/0002_photo.up.sql b/api/database/migrations/0002_photo.up.sql index 0ef424b3..4b4046fd 100644 --- a/api/database/migrations/0002_photo.up.sql +++ b/api/database/migrations/0002_photo.up.sql @@ -1,12 +1,28 @@ CREATE TABLE IF NOT EXISTS photo_url ( url_id int NOT NULL AUTO_INCREMENT, - path varchar(256) NUT NULL, + token varchar(256) NOT NULL, width int NOT NULL, height int NOT NULL, PRIMARY KEY (url_id) ); +CREATE TABLE IF NOT EXISTS photo_exif ( + exif_id int NOT NULL AUTO_INCREMENT, + camera varchar(256), + maker varchar(256), + lens varchar(256), + dateShot timestamp, + file_size_bytes bigint, + exposure varchar(256), + aperature float, + iso int(6), + focal_length float, + flash varchar(256), + + PRIMARY KEY (exif_id) +); + CREATE TABLE IF NOT EXISTS album ( album_id int NOT NULL AUTO_INCREMENT, title varchar(256) NOT NULL, @@ -23,12 +39,14 @@ CREATE TABLE IF NOT EXISTS photo ( photo_id int NOT NULL AUTO_INCREMENT, title varchar(256) NOT NULL, path varchar(512) NOT NULL UNIQUE, - -- original_url int NOT NULL, - -- thumbnail_url int NOT NULL, + original_url int NOT NULL, + thumbnail_url int NOT NULL, album_id int NOT NULL, - -- exif_id int NOT NULL, + exif_id int, PRIMARY KEY (photo_id), + FOREIGN KEY (album_id) REFERENCES album(album_id), + FOREIGN KEY (exif_id) REFERENCES photo_exif(exif_id), FOREIGN KEY (original_url) REFERENCES photo_url(url_id), FOREIGN KEY (thumbnail_url) REFERENCES photo_url(url_id) -); \ No newline at end of file +); diff --git a/api/graphql/generated.go b/api/graphql/generated.go index eaa5bc11..216fae0e 100644 --- a/api/graphql/generated.go +++ b/api/graphql/generated.go @@ -10,6 +10,7 @@ import ( "strconv" "sync" "sync/atomic" + "time" "github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/graphql/introspection" @@ -70,6 +71,7 @@ type ComplexityRoot struct { Photo struct { Album func(childComplexity int) int + Exif func(childComplexity int) int ID func(childComplexity int) int Original func(childComplexity int) int Path func(childComplexity int) int @@ -77,6 +79,20 @@ type ComplexityRoot struct { Title func(childComplexity int) int } + 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 + } + PhotoURL struct { Height func(childComplexity int) int URL func(childComplexity int) int @@ -257,6 +273,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Photo.Album(childComplexity), true + case "Photo.exif": + if e.complexity.Photo.Exif == nil { + break + } + + return e.complexity.Photo.Exif(childComplexity), true + case "Photo.id": if e.complexity.Photo.ID == nil { break @@ -292,6 +315,83 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Photo.Title(childComplexity), true + case "PhotoEXIF.aperture": + if e.complexity.PhotoExif.Aperture == nil { + break + } + + return e.complexity.PhotoExif.Aperture(childComplexity), true + + case "PhotoEXIF.camera": + if e.complexity.PhotoExif.Camera == nil { + break + } + + return e.complexity.PhotoExif.Camera(childComplexity), true + + case "PhotoEXIF.dateShot": + if e.complexity.PhotoExif.DateShot == nil { + break + } + + return e.complexity.PhotoExif.DateShot(childComplexity), true + + case "PhotoEXIF.exposure": + if e.complexity.PhotoExif.Exposure == nil { + break + } + + return e.complexity.PhotoExif.Exposure(childComplexity), true + + case "PhotoEXIF.fileSize": + if e.complexity.PhotoExif.FileSize == nil { + break + } + + return e.complexity.PhotoExif.FileSize(childComplexity), true + + case "PhotoEXIF.flash": + if e.complexity.PhotoExif.Flash == nil { + break + } + + return e.complexity.PhotoExif.Flash(childComplexity), true + + case "PhotoEXIF.focalLength": + if e.complexity.PhotoExif.FocalLength == nil { + break + } + + return e.complexity.PhotoExif.FocalLength(childComplexity), true + + case "PhotoEXIF.iso": + if e.complexity.PhotoExif.Iso == nil { + break + } + + return e.complexity.PhotoExif.Iso(childComplexity), true + + case "PhotoEXIF.lens": + if e.complexity.PhotoExif.Lens == nil { + break + } + + return e.complexity.PhotoExif.Lens(childComplexity), true + + case "PhotoEXIF.maker": + if e.complexity.PhotoExif.Maker == nil { + break + } + + return e.complexity.PhotoExif.Maker(childComplexity), true + + case "PhotoEXIF.photo": + if e.complexity.PhotoExif.Photo == nil { + break + } + + return e.complexity.PhotoExif.Photo(childComplexity), true + case "PhotoURL.height": if e.complexity.PhotoURL.Height == nil { break @@ -574,11 +674,35 @@ type Photo { thumbnail: PhotoURL "The album that holds the photo" album: Album! - # exif: PhotoEXIF + exif: PhotoEXIF # shares: [ShareToken] # downloads: [PhotoDownload] } + +"EXIF metadata from the camera" +type PhotoEXIF { + photo: Photo + "The model name of the camera" + camera: String + "The maker of the camera" + maker: String + "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" + aperture: Float + "The ISO setting of the image" + iso: Int + "The focal length of the lens, when the image was taken" + focalLength: String + "A formatted description of the flash settings, when the image was taken" + flash: String +} `}, ) @@ -1461,6 +1585,414 @@ func (ec *executionContext) _Photo_album(ctx context.Context, field graphql.Coll return ec.marshalNAlbum2ᚖgithubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐAlbum(ctx, field.Selections, res) } +func (ec *executionContext) _Photo_exif(ctx context.Context, field graphql.CollectedField, obj *models.Photo) (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: "Photo", + 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.Exif, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + 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) +} + +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: 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.Photo, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*models.Photo) + rctx.Result = res + ctx = ec.Tracer.StartFieldChildExecution(ctx) + 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) { + 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.Camera, 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_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 { + 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.Maker, 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_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 { + 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.Lens, 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_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 { + 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.DateShot, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*time.Time) + rctx.Result = res + ctx = ec.Tracer.StartFieldChildExecution(ctx) + 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) { + 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.Exposure, 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_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 { + 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.Aperture, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*float64) + rctx.Result = res + ctx = ec.Tracer.StartFieldChildExecution(ctx) + 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) { + 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.Iso, 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) _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 { + 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.FocalLength, 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_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 { + 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.Flash, 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) _PhotoURL_url(ctx context.Context, field graphql.CollectedField, obj *models.PhotoURL) (ret graphql.Marshaler) { ctx = ec.Tracer.StartFieldExecution(ctx, field) defer func() { @@ -3499,6 +4031,52 @@ func (ec *executionContext) _Photo(ctx context.Context, sel ast.SelectionSet, ob if out.Values[i] == graphql.Null { invalids++ } + case "exif": + out.Values[i] = ec._Photo_exif(ctx, field, obj) + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + +var photoEXIFImplementors = []string{"PhotoEXIF"} + +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) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("PhotoEXIF") + case "photo": + out.Values[i] = ec._PhotoEXIF_photo(ctx, field, obj) + case "camera": + out.Values[i] = ec._PhotoEXIF_camera(ctx, field, obj) + case "maker": + out.Values[i] = ec._PhotoEXIF_maker(ctx, field, obj) + case "lens": + 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": + out.Values[i] = ec._PhotoEXIF_aperture(ctx, field, obj) + case "iso": + out.Values[i] = ec._PhotoEXIF_iso(ctx, field, obj) + case "focalLength": + out.Values[i] = ec._PhotoEXIF_focalLength(ctx, field, obj) + case "flash": + out.Values[i] = ec._PhotoEXIF_flash(ctx, field, obj) default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -4515,6 +5093,17 @@ 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 { + 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 { + if v == nil { + return graphql.Null + } + return ec._PhotoEXIF(ctx, sel, v) +} + func (ec *executionContext) marshalOPhotoURL2githubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐPhotoURL(ctx context.Context, sel ast.SelectionSet, v models.PhotoURL) graphql.Marshaler { return ec._PhotoURL(ctx, sel, &v) } @@ -4549,6 +5138,29 @@ func (ec *executionContext) marshalOString2ᚖstring(ctx context.Context, sel as return ec.marshalOString2string(ctx, sel, *v) } +func (ec *executionContext) unmarshalOTime2timeᚐTime(ctx context.Context, v interface{}) (time.Time, error) { + return graphql.UnmarshalTime(v) +} + +func (ec *executionContext) marshalOTime2timeᚐTime(ctx context.Context, sel ast.SelectionSet, v time.Time) graphql.Marshaler { + return graphql.MarshalTime(v) +} + +func (ec *executionContext) unmarshalOTime2ᚖtimeᚐTime(ctx context.Context, v interface{}) (*time.Time, error) { + if v == nil { + return nil, nil + } + res, err := ec.unmarshalOTime2timeᚐTime(ctx, v) + return &res, err +} + +func (ec *executionContext) marshalOTime2ᚖtimeᚐTime(ctx context.Context, sel ast.SelectionSet, v *time.Time) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec.marshalOTime2timeᚐTime(ctx, sel, *v) +} + func (ec *executionContext) marshalOUser2githubᚗcomᚋviktorstrateᚋphotoviewᚋapiᚋgraphqlᚋmodelsᚐUser(ctx context.Context, sel ast.SelectionSet, v models.User) graphql.Marshaler { return ec._User(ctx, sel, &v) } diff --git a/api/graphql/models/generated.go b/api/graphql/models/generated.go index ebba883c..8a4db555 100644 --- a/api/graphql/models/generated.go +++ b/api/graphql/models/generated.go @@ -2,6 +2,10 @@ package models +import ( + "time" +) + type Album struct { ID string `json:"id"` Title *string `json:"title"` @@ -28,7 +32,32 @@ type Photo struct { // URL to display the photo in a smaller resolution Thumbnail *PhotoURL `json:"thumbnail"` // The album that holds the photo - Album *Album `json:"album"` + Album *Album `json:"album"` + Exif *PhotoExif `json:"exif"` +} + +// 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 PhotoURL struct { diff --git a/api/graphql/models/user.go b/api/graphql/models/user.go index 9f1114d2..3ff58aea 100644 --- a/api/graphql/models/user.go +++ b/api/graphql/models/user.go @@ -35,11 +35,7 @@ func NewUserFromRow(row *sql.Row) (*User, error) { user := User{} if err := row.Scan(&user.UserID, &user.Username, &user.Password, &user.RootPath, &user.Admin); err != nil { - if err == sql.ErrNoRows { - return nil, ErrorInvalidUserCredentials - } else { - return nil, err - } + return nil, err } return &user, nil @@ -64,7 +60,11 @@ func AuthorizeUser(database *sql.DB, username string, password string) (*User, e user, err := NewUserFromRow(row) if err != nil { - return nil, err + if err == sql.ErrNoRows { + return nil, ErrorInvalidUserCredentials + } else { + return nil, err + } } if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil { @@ -107,7 +107,7 @@ func (user *User) GenerateAccessToken(database *sql.DB) (*AccessToken, error) { if _, err := rand.Read(bytes); err != nil { return nil, errors.New(fmt.Sprintf("Could not generate token: %s\n", err.Error())) } - const CHARACTERS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-" + const CHARACTERS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" for i, b := range bytes { bytes[i] = CHARACTERS[b%byte(len(CHARACTERS))] } diff --git a/api/graphql/schema.graphql b/api/graphql/schema.graphql index 86abc933..18140848 100644 --- a/api/graphql/schema.graphql +++ b/api/graphql/schema.graphql @@ -89,8 +89,32 @@ type Photo { thumbnail: PhotoURL "The album that holds the photo" album: Album! - # exif: PhotoEXIF + exif: PhotoEXIF # shares: [ShareToken] # downloads: [PhotoDownload] } + +"EXIF metadata from the camera" +type PhotoEXIF { + photo: Photo + "The model name of the camera" + camera: String + "The maker of the camera" + maker: String + "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" + aperture: Float + "The ISO setting of the image" + iso: Int + "The focal length of the lens, when the image was taken" + focalLength: String + "A formatted description of the flash settings, when the image was taken" + flash: String +} diff --git a/api/scanner/process_image.go b/api/scanner/process_image.go new file mode 100644 index 00000000..ee024c53 --- /dev/null +++ b/api/scanner/process_image.go @@ -0,0 +1,15 @@ +package scanner + +import ( + "database/sql" + "path" +) + +func ProcessImage(tx *sql.Tx, photoPath string, albumId int) error { + photoName := path.Base(photoPath) + + _, err := tx.Exec("INSERT IGNORE INTO photo (title, path, album_id) VALUES (?, ?, ?)", photoName, photoPath, albumId) + if err != nil { + return err + } +} diff --git a/api/scanner/scanner.go b/api/scanner/scanner.go index b2189c47..184b0d62 100644 --- a/api/scanner/scanner.go +++ b/api/scanner/scanner.go @@ -84,7 +84,11 @@ func scan(database *sql.DB, user *models.User) { photoPath := path.Join(albumPath, item.Name()) if !item.IsDir() && isPathImage(photoPath) { - tx.Exec("INSERT IGNORE INTO photo (title, path, original_url, thumbnail_url, album_id)") + if err := ProcessImage(tx, photoPath, albumId); err != nil { + fmt.Printf("ERROR: Could not proccess image %s: %s", photoPath, err) + tx.Rollback() + return + } } } @@ -144,6 +148,7 @@ func directoryContainsPhotos(rootPath string) bool { var supported_mimetypes = [...]string{ "image/jpeg", "image/png", + "image/tiff", "image/x-canon-cr2", "image/bmp", }