From 11896a246ed0a32ecf9f93ff1ce430aca87bfd0f Mon Sep 17 00:00:00 2001 From: viktorstrate Date: Sun, 2 Feb 2020 18:18:38 +0100 Subject: [PATCH] Work on image processing --- .gitignore | 1 + api/go.mod | 1 + api/go.sum | 2 + api/scanner/process_image.go | 98 +++++++++++++++++++++++++++++++++++- api/scanner/scanner.go | 27 ++++++---- 5 files changed, 119 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index be2659b5..fd8974cf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # See https://help.github.com/ignore-files/ for more about ignoring files. cache/ +image-cache/ /photos_path .env diff --git a/api/go.mod b/api/go.mod index eeb37f3c..daccacb7 100644 --- a/api/go.mod +++ b/api/go.mod @@ -10,6 +10,7 @@ require ( github.com/h2non/filetype v1.0.10 github.com/joho/godotenv v1.3.0 github.com/lib/pq v1.3.0 + github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/vektah/gqlparser v1.2.0 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 ) diff --git a/api/go.sum b/api/go.sum index 504b9d12..1b879df3 100644 --- a/api/go.sum +++ b/api/go.sum @@ -28,6 +28,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU= github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= +github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= diff --git a/api/scanner/process_image.go b/api/scanner/process_image.go index ee024c53..1f8a0b08 100644 --- a/api/scanner/process_image.go +++ b/api/scanner/process_image.go @@ -2,14 +2,110 @@ package scanner import ( "database/sql" + "image" + "image/jpeg" + "log" + "math/rand" + "os" "path" + "strconv" + + "github.com/nfnt/resize" ) func ProcessImage(tx *sql.Tx, photoPath string, albumId int) error { + + log.Printf("Processing image: %s\n", photoPath) + photoName := path.Base(photoPath) - _, err := tx.Exec("INSERT IGNORE INTO photo (title, path, album_id) VALUES (?, ?, ?)", photoName, photoPath, albumId) + // Check if image already exists + row := tx.QueryRow("SELECT (photo_id) FROM photo WHERE path = ?", photoPath) + var id int + if err := row.Scan(&id); err != sql.ErrNoRows { + if err == nil { + log.Printf("Image already processed: %s\n", photoPath) + return nil + } else { + return err + } + } + + thumbFile, err := os.Open(photoPath) if err != nil { return err } + defer thumbFile.Close() + + image, _, err := image.Decode(thumbFile) + if err != nil { + log.Println("ERROR: decoding image") + return err + } + thumbnailImage := resize.Thumbnail(1024, 1024, image, resize.Bilinear) + + if _, err := os.Stat("image-cache"); os.IsNotExist(err) { + if err := os.Mkdir("image-cache", os.ModePerm); err != nil { + log.Println("ERROR: Could not make image cache directory") + return err + } + } + + // Make album cache dir + albumCachePath := path.Join("image-cache", strconv.Itoa(albumId)) + if _, err := os.Stat(albumCachePath); os.IsNotExist(err) { + if err := os.Mkdir(albumCachePath, os.ModePerm); err != nil { + log.Println("ERROR: Could not make album image cache directory") + return err + } + } + // Generate image token name + thumbnailToken := generateToken() + originalToken := generateToken() + + // Save thumbnail as jpg + thumbFile, err = os.Create(path.Join(albumCachePath, thumbnailToken+".jpg")) + if err != nil { + log.Println("ERROR: Could not make thumbnail file") + return err + } + defer thumbFile.Close() + + jpeg.Encode(thumbFile, thumbnailImage, &jpeg.Options{Quality: 70}) + + thumbSize := thumbnailImage.Bounds().Max + thumbRes, err := tx.Exec("INSERT INTO photo_url (token, width, height) VALUES (?, ?, ?)", thumbnailToken, thumbSize.X, thumbSize.Y) + if err != nil { + return err + } + thumbUrlId, err := thumbRes.LastInsertId() + if err != nil { + return err + } + + origSize := image.Bounds().Max + origRes, err := tx.Exec("INSERT INTO photo_url (token, width, height) VALUES (?, ?, ?)", originalToken, origSize.X, origSize.Y) + if err != nil { + return err + } + origUrlId, err := origRes.LastInsertId() + + _, err = tx.Exec("INSERT INTO photo (title, path, album_id, original_url, thumbnail_url) VALUES (?, ?, ?, ?, ?)", photoName, photoPath, albumId, origUrlId, thumbUrlId) + if err != nil { + log.Printf("ERROR: Could not insert photo into database") + return err + } + + return nil +} + +func generateToken() string { + const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + const length = 24 + + b := make([]byte, length) + for i := range b { + b[i] = charset[rand.Intn(len(charset))] + } + return string(b) } diff --git a/api/scanner/scanner.go b/api/scanner/scanner.go index 184b0d62..86a85ca9 100644 --- a/api/scanner/scanner.go +++ b/api/scanner/scanner.go @@ -79,23 +79,31 @@ func scan(database *sql.DB, user *models.User) { return } + // Commit album transaction + if err := tx.Commit(); err != nil { + log.Printf("ERROR: Could not commit database transaction: %s\n", err) + return + } + // Scan for photos for _, item := range dirContent { photoPath := path.Join(albumPath, item.Name()) if !item.IsDir() && isPathImage(photoPath) { + tx, err := database.Begin() + if err != nil { + log.Printf("ERROR: Could not begin database transaction for image %s: %s\n", photoPath, err) + return + } + if err := ProcessImage(tx, photoPath, albumId); err != nil { - fmt.Printf("ERROR: Could not proccess image %s: %s", photoPath, err) + log.Printf("ERROR: processing image %s: %s", photoPath, err) tx.Rollback() return } - } - } - // Commit album and photos transaction - if err := tx.Commit(); err != nil { - log.Printf("ERROR: Could not commit database transaction: %s\n", err) - return + tx.Commit() + } } // Scan for sub-albums @@ -148,8 +156,8 @@ func directoryContainsPhotos(rootPath string) bool { var supported_mimetypes = [...]string{ "image/jpeg", "image/png", - "image/tiff", - "image/x-canon-cr2", + // "image/tiff", + // "image/x-canon-cr2", "image/bmp", } @@ -159,6 +167,7 @@ func isPathImage(path string) bool { log.Printf("Could not open file %s: %s\n", path, err) return false } + defer file.Close() head := make([]byte, 261) if _, err := file.Read(head); err != nil {