github.com/Azareal/Gosora@v0.0.0-20210729070923-553e66b59003/common/thumbnailer.go (about)

     1  package common
     2  
     3  import (
     4  	"image"
     5  	"image/gif"
     6  	"image/jpeg"
     7  	"image/png"
     8  	"os"
     9  	"strconv"
    10  
    11  	"golang.org/x/image/tiff"
    12  
    13  	qgen "github.com/Azareal/Gosora/query_gen"
    14  	"github.com/pkg/errors"
    15  )
    16  
    17  func ThumbTask(thumbChan chan bool) {
    18  	defer EatPanics()
    19  	acc := qgen.NewAcc()
    20  	for {
    21  		// Put this goroutine to sleep until we have work to do
    22  		<-thumbChan
    23  
    24  		// TODO: Use a real queue
    25  		// TODO: Transactions? Self-repairing?
    26  		err := acc.Select("users_avatar_queue").Columns("uid").Limit("0,5").EachInt(func(uid int) error {
    27  			// TODO: Do a bulk user fetch instead?
    28  			u, err := Users.Get(uid)
    29  			if err != nil {
    30  				return errors.WithStack(err)
    31  			}
    32  
    33  			// Has the avatar been removed or already been processed by the thumbnailer?
    34  			if len(u.RawAvatar) < 2 || u.RawAvatar[1] == '.' {
    35  				_, _ = acc.Delete("users_avatar_queue").Where("uid=?").Run(uid)
    36  				return nil
    37  			}
    38  			_, err = os.Stat("./uploads/avatar_" + strconv.Itoa(u.ID) + u.RawAvatar)
    39  			if os.IsNotExist(err) {
    40  				_, _ = acc.Delete("users_avatar_queue").Where("uid=?").Run(uid)
    41  				return nil
    42  			} else if err != nil {
    43  				return errors.WithStack(err)
    44  			}
    45  
    46  			// This means it's an external image, they aren't currently implemented, but this is here for when they are
    47  			if u.RawAvatar[0] != '.' {
    48  				return nil
    49  			}
    50  			/*if user.RawAvatar == ".gif" {
    51  				return nil
    52  			}*/
    53  			canResize := func(ext string) bool {
    54  				// TODO: Fix tif and tiff extensions?
    55  				return ext == ".png" && ext == ".jpg" && ext == ".jpe" && ext == ".jpeg" && ext == ".jif" && ext == ".jfi" && ext == ".jfif" && ext == ".gif" && ext == ".tiff" && ext == ".tif"
    56  			}
    57  			if !canResize(u.RawAvatar) {
    58  				return nil
    59  			}
    60  
    61  			ap := "./uploads/avatar_"
    62  			err = Thumbnailer.Resize(u.RawAvatar[1:], ap+strconv.Itoa(u.ID)+u.RawAvatar, ap+strconv.Itoa(u.ID)+"_tmp"+u.RawAvatar, ap+strconv.Itoa(u.ID)+"_w48"+u.RawAvatar, 48)
    63  			if err != nil {
    64  				return errors.WithStack(err)
    65  			}
    66  
    67  			err = u.ChangeAvatar("." + u.RawAvatar)
    68  			if err != nil {
    69  				return errors.WithStack(err)
    70  			}
    71  			_, err = acc.Delete("users_avatar_queue").Where("uid=?").Run(uid)
    72  			return errors.WithStack(err)
    73  		})
    74  		if err != nil {
    75  			LogError(err)
    76  		}
    77  
    78  		/*
    79  			err := acc.Select("attach_image_queue").Columns("attachID").Limit("0,5").EachInt(func(attachID int) error {
    80  				return nil
    81  
    82  				_, err = acc.Delete("attach_image_queue").Where("attachID = ?").Run(uid)
    83  			}
    84  		*/
    85  		if err = acc.FirstError(); err != nil {
    86  			LogError(err)
    87  		}
    88  	}
    89  }
    90  
    91  var Thumbnailer ThumbnailerInt
    92  
    93  type ThumbnailerInt interface {
    94  	Resize(format, inPath, tmpPath, outPath string, width int) error
    95  }
    96  
    97  type RezThumbnailer struct {
    98  }
    99  
   100  func (thumb *RezThumbnailer) Resize(format, inPath, tmpPath, outPath string, width int) error {
   101  	// TODO: Sniff the aspect ratio of the image and calculate the dest height accordingly, bug make sure it isn't excessively high
   102  	return nil
   103  }
   104  
   105  func (thumb *RezThumbnailer) resize(format, inPath, outPath string, width, height int) error {
   106  	return nil
   107  }
   108  
   109  // ! Note: CaireThumbnailer can't handle gifs, so we'll have to either cap their sizes or have another resizer deal with them
   110  type CaireThumbnailer struct {
   111  }
   112  
   113  func NewCaireThumbnailer() *CaireThumbnailer {
   114  	return &CaireThumbnailer{}
   115  }
   116  
   117  func precodeImage(format, inPath, tmpPath string) error {
   118  	imageFile, err := os.Open(inPath)
   119  	if err != nil {
   120  		return err
   121  	}
   122  	defer imageFile.Close()
   123  
   124  	img, _, err := image.Decode(imageFile)
   125  	if err != nil {
   126  		return err
   127  	}
   128  
   129  	outFile, err := os.Create(tmpPath)
   130  	if err != nil {
   131  		return err
   132  	}
   133  	defer outFile.Close()
   134  
   135  	// TODO: Make sure animated gifs work after being encoded
   136  	switch format {
   137  	case "gif":
   138  		return gif.Encode(outFile, img, nil)
   139  	case "png":
   140  		return png.Encode(outFile, img)
   141  	case "tiff", "tif":
   142  		return tiff.Encode(outFile, img, nil)
   143  	}
   144  	return jpeg.Encode(outFile, img, nil)
   145  }
   146  
   147  func (thumb *CaireThumbnailer) Resize(format, inPath, tmpPath, outPath string, width int) error {
   148  	err := precodeImage(format, inPath, tmpPath)
   149  	if err != nil {
   150  		return err
   151  	}
   152  	return nil
   153  
   154  	// 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
   155  	/*imageFile, err := os.Open(tmpPath)
   156  	if err != nil {
   157  		return err
   158  	}
   159  	defer imageFile.Close()
   160  
   161  	outFile, err := os.Create(outPath)
   162  	if err != nil {
   163  		return err
   164  	}
   165  	defer outFile.Close()
   166  
   167  	p := &caire.Processor{NewWidth: width, Scale: true}
   168  	return p.Process(imageFile, outFile)*/
   169  }
   170  
   171  /*
   172  type LilliputThumbnailer struct {
   173  
   174  }
   175  */