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 */