1
Fork 0

Add blurhash generation

This commit is contained in:
viktorstrate 2022-02-01 22:02:08 +01:00
parent 8868b4a584
commit b4ad1c4f88
No known key found for this signature in database
GPG Key ID: 3F855605109C1E8A
5 changed files with 136 additions and 4 deletions

View File

@ -7,6 +7,7 @@ require (
github.com/Kagami/go-face v0.0.0-20210630145111-0c14797b4d0e
github.com/agnivade/levenshtein v1.1.1 // indirect
github.com/barasher/go-exiftool v1.7.0
github.com/buckket/go-blurhash v1.1.0 // indirect
github.com/disintegration/imaging v1.6.2
github.com/felixge/httpsnoop v1.0.2 // indirect
github.com/go-sql-driver/mysql v1.6.0

View File

@ -15,6 +15,8 @@ github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
github.com/barasher/go-exiftool v1.7.0 h1:EOGb5D6TpWXmqsnEjJ0ai6+tIW2gZFwIoS9O/33Nixs=
github.com/barasher/go-exiftool v1.7.0/go.mod h1:F9s/a3uHSM8YniVfwF+sbQUtP8Gmh9nyzigNF+8vsWo=
github.com/buckket/go-blurhash v1.1.0 h1:X5M6r0LIvwdvKiUtiNcRL2YlmOfMzYobI3VCKCZc9Do=
github.com/buckket/go-blurhash v1.1.0/go.mod h1:aT2iqo5W9vu9GpyoLErKfTHwgODsZp3bQfXjXJUxNb8=
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=

View File

@ -29,6 +29,7 @@ type Media struct {
SideCarPath *string
SideCarHash *string `gorm:"unique"`
Faces []*ImageFace `gorm:"constraint:OnDelete:CASCADE;"`
Blurhash *string `gorm:""`
// Only used internally
CounterpartPath *string `gorm:"-"`
@ -49,6 +50,21 @@ func (m *Media) Date() time.Time {
return m.DateShot
}
func (m *Media) Thumbnail() (*MediaURL, error) {
if len(m.MediaURL) == 0 {
return nil, errors.New("media.MediaURL is empty")
}
for _, url := range m.MediaURL {
if url.Purpose == PhotoThumbnail || url.Purpose == VideoThumbnail {
url.Media = m
return &url, nil
}
}
return nil, nil
}
type MediaType string
const (

View File

@ -9,6 +9,7 @@ import (
"github.com/photoview/photoview/api/graphql/models"
"github.com/photoview/photoview/api/graphql/notification"
"github.com/photoview/photoview/api/scanner/scanner_cache"
"github.com/photoview/photoview/api/scanner/scanner_utils"
"github.com/photoview/photoview/api/utils"
"github.com/pkg/errors"
"gorm.io/gorm"
@ -156,8 +157,20 @@ func (queue *ScannerQueue) processQueue(notifyThrottle *utils.Throttle) {
notification.BroadcastNotification(&models.Notification{
Key: "global-scanner-progress",
Type: models.NotificationTypeMessage,
Header: fmt.Sprintf("Scanner complete"),
Content: fmt.Sprintf("All jobs have been scanned"),
Header: "Generating blurhashes",
Content: "Generating blurhashes for newly scanned media",
Positive: true,
})
if err := GenerateBlurhashes(queue.db); err != nil {
scanner_utils.ScannerError("Failed to generate blurhashes: %v", err)
}
notification.BroadcastNotification(&models.Notification{
Key: "global-scanner-progress",
Type: models.NotificationTypeMessage,
Header: "Scanner complete",
Content: "All jobs have been scanned",
Positive: true,
})
} else {
@ -165,7 +178,7 @@ func (queue *ScannerQueue) processQueue(notifyThrottle *utils.Throttle) {
notification.BroadcastNotification(&models.Notification{
Key: "global-scanner-progress",
Type: models.NotificationTypeMessage,
Header: fmt.Sprintf("Scanning media"),
Header: "Scanning media",
Content: fmt.Sprintf("%d jobs in progress\n%d jobs waiting", in_progress_length, up_next_length),
})
})
@ -191,7 +204,9 @@ func AddAllToQueue() error {
}
for _, user := range users {
AddUserToQueue(user)
if err := AddUserToQueue(user); err != nil {
return errors.Wrapf(err, "failed to add user for scanning (%d)", user.ID)
}
}
return nil

View File

@ -0,0 +1,98 @@
package scanner
import (
"fmt"
"image"
"log"
"os"
"github.com/buckket/go-blurhash"
"github.com/photoview/photoview/api/graphql/models"
"gorm.io/gorm"
)
// GenerateBlurhashes queries the database for media that are missing a blurhash and computes one for them.
// This function blocks until all hashes have been computed
func GenerateBlurhashes(db *gorm.DB) error {
var results []*models.Media
processErrors := make([]error, 0)
query := db.Model(&models.Media{}).
Preload("MediaURL").
Joins("INNER JOIN media_urls ON media.id = media_urls.media_id").
Where("blurhash IS NULL").
Where("media_urls.purpose = 'thumbnail' OR media_urls.purpose = 'video-thumbnail'")
err := query.FindInBatches(&results, 100, func(tx *gorm.DB, batch int) error {
log.Printf("generating %d blurhashes", len(results))
hashes := make([]*string, len(results))
for i, row := range results {
thumbnail, err := row.Thumbnail()
if err != nil {
log.Printf("failed to get thumbnail for media to generate blurhash (%d): %v", row.ID, err)
processErrors = append(processErrors, err)
continue
}
hashStr, err := GenerateBlurhashFromThumbnail(thumbnail)
if err != nil {
log.Printf("failed to generate blurhash for media (%d): %v", row.ID, err)
processErrors = append(processErrors, err)
continue
}
hashes[i] = &hashStr
results[i].Blurhash = &hashStr
}
tx.Save(results)
// if err := db.Update("blurhash", hashes).Error; err != nil {
// return err
// }
return nil
}).Error
if err != nil {
return err
}
if len(processErrors) == 0 {
return nil
} else {
return fmt.Errorf("failed to generate %d blurhashes", len(processErrors))
}
}
// GenerateBlurhashFromThumbnail generates a blurhash for a single media and stores it in the database
func GenerateBlurhashFromThumbnail(thumbnail *models.MediaURL) (string, error) {
thumbnail_path, err := thumbnail.CachedPath()
if err != nil {
return "", err
}
imageFile, err := os.Open(thumbnail_path)
if err != nil {
return "", err
}
imageData, _, err := image.Decode(imageFile)
if err != nil {
return "", err
}
hashStr, err := blurhash.Encode(4, 3, imageData)
if err != nil {
return "", err
}
// if err := db.Model(&models.Media{}).Where("id = ?", thumbnail.MediaID).Update("blurhash", hashStr).Error; err != nil {
// return "", err
// }
return hashStr, nil
}