gosora/common/thumbnailer.go
2019-10-30 18:16:19 +10:00

170 lines
4.5 KiB
Go

package common
import (
"image"
"image/gif"
"image/jpeg"
"image/png"
"os"
"strconv"
"golang.org/x/image/tiff"
qgen "github.com/Azareal/Gosora/query_gen"
"github.com/pkg/errors"
)
func ThumbTask(thumbChan chan bool) {
for {
// Put this goroutine to sleep until we have work to do
<-thumbChan
// TODO: Use a real queue
// TODO: Transactions? Self-repairing?
acc := qgen.NewAcc()
err := acc.Select("users_avatar_queue").Columns("uid").Limit("0,5").EachInt(func(uid int) error {
// TODO: Do a bulk user fetch instead?
user, err := Users.Get(uid)
if err != nil {
return errors.WithStack(err)
}
// Has the avatar been removed or already been processed by the thumbnailer?
if len(user.RawAvatar) < 2 || user.RawAvatar[1] == '.' {
_, _ = acc.Delete("users_avatar_queue").Where("uid = ?").Run(uid)
return nil
}
_, err = os.Stat("./uploads/avatar_" + strconv.Itoa(user.ID) + user.RawAvatar)
if os.IsNotExist(err) {
_, _ = acc.Delete("users_avatar_queue").Where("uid = ?").Run(uid)
return nil
} else if err != nil {
return errors.WithStack(err)
}
// This means it's an external image, they aren't currently implemented, but this is here for when they are
if user.RawAvatar[0] != '.' {
return nil
}
/*if user.RawAvatar == ".gif" {
return nil
}*/
if user.RawAvatar != ".png" && user.RawAvatar != ".jpg" && user.RawAvatar != ".jpe" && user.RawAvatar != ".jpeg" && user.RawAvatar != ".jif" && user.RawAvatar != ".jfi" && user.RawAvatar != ".jfif" && user.RawAvatar != ".gif" && user.RawAvatar != "tiff" && user.RawAvatar != "tif" {
return nil
}
err = Thumbnailer.Resize(user.RawAvatar[1:], "./uploads/avatar_"+strconv.Itoa(user.ID)+user.RawAvatar, "./uploads/avatar_"+strconv.Itoa(user.ID)+"_tmp"+user.RawAvatar, "./uploads/avatar_"+strconv.Itoa(user.ID)+"_w48"+user.RawAvatar, 48)
if err != nil {
return errors.WithStack(err)
}
err = user.ChangeAvatar("." + user.RawAvatar)
if err != nil {
return errors.WithStack(err)
}
_, err = acc.Delete("users_avatar_queue").Where("uid = ?").Run(uid)
return errors.WithStack(err)
})
if err != nil {
LogError(err)
}
/*
err := acc.Select("attach_image_queue").Columns("attachID").Limit("0,5").EachInt(func(attachID int) error {
return nil
_, err = acc.Delete("attach_image_queue").Where("attachID = ?").Run(uid)
}
*/
if err = acc.FirstError(); err != nil {
LogError(err)
}
}
}
var Thumbnailer ThumbnailerInt
type ThumbnailerInt interface {
Resize(format string, inPath string, tmpPath string, outPath string, width int) error
}
type RezThumbnailer struct {
}
func (thumb *RezThumbnailer) Resize(format string, inPath string, tmpPath string, outPath string, width int) error {
// TODO: Sniff the aspect ratio of the image and calculate the dest height accordingly, bug make sure it isn't excessively high
return nil
}
func (thumb *RezThumbnailer) resize(format string, inPath string, outPath string, width int, height int) error {
return nil
}
// ! Note: CaireThumbnailer can't handle gifs, so we'll have to either cap their sizes or have another resizer deal with them
type CaireThumbnailer struct {
}
func NewCaireThumbnailer() *CaireThumbnailer {
return &CaireThumbnailer{}
}
func precodeImage(format string, inPath string, tmpPath string) error {
imageFile, err := os.Open(inPath)
if err != nil {
return err
}
defer imageFile.Close()
img, _, err := image.Decode(imageFile)
if err != nil {
return err
}
outFile, err := os.Create(tmpPath)
if err != nil {
return err
}
defer outFile.Close()
// TODO: Make sure animated gifs work after being encoded
switch format {
case "gif":
return gif.Encode(outFile, img, nil)
case "png":
return png.Encode(outFile, img)
case "tiff", "tif":
return tiff.Encode(outFile, img, nil)
}
return jpeg.Encode(outFile, img, nil)
}
func (thumb *CaireThumbnailer) Resize(format string, inPath string, tmpPath string, outPath string, width int) error {
err := precodeImage(format, inPath, tmpPath)
if err != nil {
return err
}
return nil
// TODO: Caire doesn't work. Try something else. Or get them to fix the index out of range. We get enough wins from re-encoding as jpeg anyway
/*imageFile, err := os.Open(tmpPath)
if err != nil {
return err
}
defer imageFile.Close()
outFile, err := os.Create(outPath)
if err != nil {
return err
}
defer outFile.Close()
p := &caire.Processor{NewWidth: width, Scale: true}
return p.Process(imageFile, outFile)*/
}
/*
type LilliputThumbnailer struct {
}
*/