1
Fork 0
photoview/api/scanner/process_photo.go

261 lines
7.7 KiB
Go
Raw Normal View History

2020-02-23 18:00:08 +01:00
package scanner
import (
"database/sql"
"fmt"
"log"
"os"
"path"
"strconv"
"strings"
"github.com/pkg/errors"
2020-02-23 18:00:08 +01:00
"github.com/viktorstrate/photoview/api/graphql/models"
"github.com/viktorstrate/photoview/api/utils"
// Image decoders
_ "image/gif"
_ "image/png"
2020-03-02 16:32:24 +01:00
_ "golang.org/x/image/bmp"
_ "golang.org/x/image/tiff"
2020-02-23 18:00:08 +01:00
_ "golang.org/x/image/webp"
)
// Higher order function used to check if MediaURL for a given MediaPurpose exists
func makePhotoURLChecker(tx *sql.Tx, mediaID int) (func(purpose models.MediaPurpose) (*models.MediaURL, error), error) {
mediaURLExistsStmt, err := tx.Prepare("SELECT * FROM media_url WHERE media_id = ? AND purpose = ?")
2020-02-23 18:00:08 +01:00
if err != nil {
return nil, err
}
return func(purpose models.MediaPurpose) (*models.MediaURL, error) {
row := mediaURLExistsStmt.QueryRow(mediaID, purpose)
mediaURL, err := models.NewMediaURLFromRow(row)
2020-02-23 18:00:08 +01:00
if err != nil {
if err == sql.ErrNoRows {
return nil, nil
}
return nil, err
}
return mediaURL, nil
2020-02-23 18:00:08 +01:00
}, nil
}
func ProcessMedia(tx *sql.Tx, media *models.Media) (bool, error) {
2020-07-11 13:13:31 +02:00
imageData := EncodeMediaData{
media: media,
2020-07-10 12:58:11 +02:00
}
2020-02-23 18:00:08 +01:00
2020-07-10 12:58:11 +02:00
contentType, err := imageData.ContentType()
if err != nil {
return false, errors.Wrapf(err, "get content-type of media (%s)", media.Path)
2020-07-10 12:58:11 +02:00
}
2020-02-23 18:00:08 +01:00
// Make sure media cache directory exists
mediaCachePath, err := makeMediaCacheDir(media)
2020-07-10 12:58:11 +02:00
if err != nil {
return false, errors.Wrap(err, "cache directory error")
}
2020-07-10 12:58:11 +02:00
if contentType.isVideo() {
return processVideo(tx, &imageData, mediaCachePath)
} else {
return processPhoto(tx, &imageData, mediaCachePath)
2020-02-23 18:00:08 +01:00
}
2020-07-10 12:58:11 +02:00
}
2020-07-11 13:13:31 +02:00
func processPhoto(tx *sql.Tx, imageData *EncodeMediaData, photoCachePath *string) (bool, error) {
2020-07-10 12:58:11 +02:00
photo := imageData.media
2020-07-10 12:58:11 +02:00
log.Printf("Processing photo: %s\n", photo.Path)
didProcess := false
2020-02-23 18:00:08 +01:00
photoUrlFromDB, err := makePhotoURLChecker(tx, photo.MediaID)
2020-02-23 18:00:08 +01:00
if err != nil {
return false, err
2020-02-23 18:00:08 +01:00
}
// original photo url
2020-07-10 12:58:11 +02:00
origURL, err := photoUrlFromDB(models.MediaOriginal)
2020-02-23 18:00:08 +01:00
if err != nil {
return false, err
2020-02-23 18:00:08 +01:00
}
// Thumbnail
thumbURL, err := photoUrlFromDB(models.PhotoThumbnail)
2020-02-23 18:00:08 +01:00
if err != nil {
2020-07-10 12:58:11 +02:00
return false, errors.Wrap(err, "error processing photo thumbnail")
2020-02-23 18:00:08 +01:00
}
// Highres
highResURL, err := photoUrlFromDB(models.PhotoHighRes)
2020-02-23 18:00:08 +01:00
if err != nil {
2020-07-10 12:58:11 +02:00
return false, errors.Wrap(err, "error processing photo highres")
2020-02-23 18:00:08 +01:00
}
// Generate high res jpeg
var photoDimensions *PhotoDimensions
var baseImagePath string = photo.Path
2020-02-23 18:00:08 +01:00
if highResURL == nil {
contentType, err := imageData.ContentType()
if err != nil {
return false, err
}
if !contentType.isWebCompatible() {
didProcess = true
highres_name := fmt.Sprintf("highres_%s_%s", path.Base(photo.Path), utils.GenerateToken())
2020-02-23 18:00:08 +01:00
highres_name = strings.ReplaceAll(highres_name, ".", "_")
highres_name = strings.ReplaceAll(highres_name, " ", "_")
highres_name = highres_name + ".jpg"
baseImagePath = path.Join(*photoCachePath, highres_name)
err = imageData.EncodeHighRes(tx, baseImagePath)
2020-02-23 18:00:08 +01:00
if err != nil {
return false, errors.Wrap(err, "creating high-res cached image")
2020-02-23 18:00:08 +01:00
}
photoDimensions, err = GetPhotoDimensions(baseImagePath)
2020-02-23 18:00:08 +01:00
if err != nil {
return false, err
2020-02-23 18:00:08 +01:00
}
_, err = tx.Exec("INSERT INTO media_url (media_id, media_name, width, height, purpose, content_type) VALUES (?, ?, ?, ?, ?, ?)",
photo.MediaID, highres_name, photoDimensions.Width, photoDimensions.Height, models.PhotoHighRes, "image/jpeg")
2020-02-23 18:00:08 +01:00
if err != nil {
2020-07-11 13:13:31 +02:00
return false, errors.Wrapf(err, "could not insert highres media url (%d, %s)", photo.MediaID, photo.Title)
2020-02-23 18:00:08 +01:00
}
}
} else {
// Verify that highres photo still exists in cache
baseImagePath = path.Join(*photoCachePath, highResURL.MediaName)
2020-02-23 18:00:08 +01:00
if _, err := os.Stat(baseImagePath); os.IsNotExist(err) {
fmt.Printf("High-res photo found in database but not in cache, re-encoding photo to cache: %s\n", highResURL.MediaName)
didProcess = true
2020-02-23 18:00:08 +01:00
err = imageData.EncodeHighRes(tx, baseImagePath)
if err != nil {
return false, errors.Wrap(err, "creating high-res cached image")
}
}
}
// Save original photo to database
if origURL == nil {
didProcess = true
// Make sure photo dimensions is set
if photoDimensions == nil {
photoDimensions, err = GetPhotoDimensions(baseImagePath)
2020-05-15 15:23:21 +02:00
if err != nil {
return false, err
2020-05-15 15:23:21 +02:00
}
}
if err = saveOriginalPhotoToDB(tx, photo, imageData, photoDimensions); err != nil {
return false, errors.Wrap(err, "saving original photo to database")
}
}
// Save thumbnail to cache
if thumbURL == nil {
didProcess = true
thumbnail_name := fmt.Sprintf("thumbnail_%s_%s", path.Base(photo.Path), utils.GenerateToken())
thumbnail_name = strings.ReplaceAll(thumbnail_name, ".", "_")
thumbnail_name = strings.ReplaceAll(thumbnail_name, " ", "_")
thumbnail_name = thumbnail_name + ".jpg"
// thumbnailImage, err := imageData.ThumbnailImage(tx)
// if err != nil {
// return err
// }
2020-05-15 15:23:21 +02:00
thumbOutputPath := path.Join(*photoCachePath, thumbnail_name)
thumbSize, err := EncodeThumbnail(baseImagePath, thumbOutputPath)
if err != nil {
return false, errors.Wrap(err, "could not create thumbnail cached image")
}
_, err = tx.Exec("INSERT INTO media_url (media_id, media_name, width, height, purpose, content_type) VALUES (?, ?, ?, ?, ?, ?)", photo.MediaID, thumbnail_name, thumbSize.Width, thumbSize.Height, models.PhotoThumbnail, "image/jpeg")
if err != nil {
return false, err
}
} else {
// Verify that thumbnail photo still exists in cache
thumbPath := path.Join(*photoCachePath, thumbURL.MediaName)
if _, err := os.Stat(thumbPath); os.IsNotExist(err) {
didProcess = true
fmt.Printf("Thumbnail photo found in database but not in cache, re-encoding photo to cache: %s\n", thumbURL.MediaName)
_, err := EncodeThumbnail(baseImagePath, thumbPath)
2020-02-23 18:00:08 +01:00
if err != nil {
return false, errors.Wrap(err, "could not create thumbnail cached image")
2020-02-23 18:00:08 +01:00
}
}
}
return didProcess, nil
2020-02-23 18:00:08 +01:00
}
func makeMediaCacheDir(photo *models.Media) (*string, error) {
2020-02-23 18:00:08 +01:00
// Make root cache dir if not exists
if _, err := os.Stat(PhotoCache()); os.IsNotExist(err) {
if err := os.Mkdir(PhotoCache(), os.ModePerm); err != nil {
2020-07-10 12:58:11 +02:00
return nil, errors.Wrap(err, "could not make root image cache directory")
2020-02-23 18:00:08 +01:00
}
}
// Make album cache dir if not exists
albumCachePath := path.Join(PhotoCache(), strconv.Itoa(photo.AlbumId))
if _, err := os.Stat(albumCachePath); os.IsNotExist(err) {
if err := os.Mkdir(albumCachePath, os.ModePerm); err != nil {
2020-07-10 12:58:11 +02:00
return nil, errors.Wrap(err, "could not make album image cache directory")
2020-02-23 18:00:08 +01:00
}
}
// Make photo cache dir if not exists
photoCachePath := path.Join(albumCachePath, strconv.Itoa(photo.MediaID))
2020-02-23 18:00:08 +01:00
if _, err := os.Stat(photoCachePath); os.IsNotExist(err) {
if err := os.Mkdir(photoCachePath, os.ModePerm); err != nil {
2020-07-10 12:58:11 +02:00
return nil, errors.Wrap(err, "could not make photo image cache directory")
2020-02-23 18:00:08 +01:00
}
}
return &photoCachePath, nil
}
2020-07-11 13:13:31 +02:00
func saveOriginalPhotoToDB(tx *sql.Tx, photo *models.Media, imageData *EncodeMediaData, photoDimensions *PhotoDimensions) error {
photoName := path.Base(photo.Path)
photoBaseName := photoName[0 : len(photoName)-len(path.Ext(photoName))]
photoBaseExt := path.Ext(photoName)
original_image_name := fmt.Sprintf("%s_%s", photoBaseName, utils.GenerateToken())
original_image_name = strings.ReplaceAll(original_image_name, " ", "_") + photoBaseExt
contentType, err := imageData.ContentType()
if err != nil {
return err
}
_, err = tx.Exec("INSERT INTO media_url (media_id, media_name, width, height, purpose, content_type) VALUES (?, ?, ?, ?, ?, ?)", photo.MediaID, original_image_name, photoDimensions.Width, photoDimensions.Height, models.MediaOriginal, contentType)
if err != nil {
log.Printf("Could not insert original photo url: %d, %s\n", photo.MediaID, photoName)
return err
}
return nil
}