2024-06-10 21:36:26 +02:00
|
|
|
package thumbnail
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"image"
|
|
|
|
"image/color"
|
|
|
|
"image/png"
|
2024-06-10 21:56:24 +02:00
|
|
|
"leech/config"
|
2024-06-10 21:36:26 +02:00
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"slices"
|
|
|
|
"sync"
|
|
|
|
|
|
|
|
"github.com/disintegration/imaging"
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
|
|
)
|
|
|
|
|
|
|
|
var SupportedFileTypes = []string{".png", ".PNG", ".jpg", ".JPG", ".jpeg", ".JPEG"}
|
|
|
|
var FileTypesMap = map[string]imaging.Format{
|
|
|
|
".png": imaging.PNG,
|
|
|
|
".PNG": imaging.PNG,
|
|
|
|
".jpg": imaging.JPEG,
|
|
|
|
".JPG": imaging.JPEG,
|
|
|
|
".jpeg": imaging.JPEG,
|
|
|
|
".JPEG": imaging.JPEG,
|
|
|
|
}
|
2024-06-10 22:10:25 +02:00
|
|
|
var thumbnailSize = 48
|
2024-06-10 21:36:26 +02:00
|
|
|
|
|
|
|
var thumbnailCache = map[string][]byte{}
|
|
|
|
var thumbnailCacheMutex = &sync.RWMutex{}
|
2024-06-10 21:55:03 +02:00
|
|
|
var memLimiterMutex = &sync.RWMutex{}
|
|
|
|
var jobCounter = 0
|
|
|
|
var checkAgain = make(chan bool, 5)
|
2024-06-10 21:36:26 +02:00
|
|
|
|
|
|
|
func IsSupportedFileType(completePath string) bool {
|
|
|
|
fileExt := filepath.Ext(completePath)
|
|
|
|
return slices.Contains(SupportedFileTypes, fileExt)
|
|
|
|
}
|
|
|
|
|
2024-06-10 21:55:03 +02:00
|
|
|
func WaitForAvailable() {
|
|
|
|
memLimiterMutex.RLock()
|
2024-06-10 21:56:24 +02:00
|
|
|
for config.Config.ThumbnailJobLimit == jobCounter {
|
2024-06-10 21:55:03 +02:00
|
|
|
memLimiterMutex.RUnlock()
|
|
|
|
<-checkAgain
|
|
|
|
memLimiterMutex.RLock()
|
|
|
|
}
|
|
|
|
memLimiterMutex.RUnlock()
|
|
|
|
memLimiterMutex.Lock()
|
|
|
|
jobCounter += 1
|
|
|
|
memLimiterMutex.Unlock()
|
|
|
|
}
|
|
|
|
|
|
|
|
func Free() {
|
|
|
|
memLimiterMutex.Lock()
|
|
|
|
jobCounter -= 1
|
|
|
|
memLimiterMutex.Unlock()
|
|
|
|
select {
|
|
|
|
case checkAgain <- true:
|
|
|
|
return
|
|
|
|
default:
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-10 21:36:26 +02:00
|
|
|
func GetThumbnail(c *fiber.Ctx, completePath string) {
|
|
|
|
c.Set(fiber.HeaderContentType, "image")
|
|
|
|
thumbnailCacheMutex.RLock()
|
|
|
|
bytesThumb, ok := thumbnailCache[completePath]
|
|
|
|
thumbnailCacheMutex.RUnlock()
|
|
|
|
if ok {
|
|
|
|
c.Write(bytesThumb)
|
|
|
|
return
|
|
|
|
}
|
2024-06-10 22:21:27 +02:00
|
|
|
WaitForAvailable()
|
2024-06-10 21:36:26 +02:00
|
|
|
|
|
|
|
fileExt := filepath.Ext(completePath)
|
|
|
|
if !slices.Contains(SupportedFileTypes, fileExt) {
|
2024-06-10 21:55:03 +02:00
|
|
|
Free()
|
2024-06-10 21:36:26 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
f, err := os.Open(completePath)
|
|
|
|
if err != nil {
|
2024-06-10 21:55:03 +02:00
|
|
|
Free()
|
2024-06-10 21:36:26 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
img, _, err := image.Decode(f)
|
|
|
|
if err != nil {
|
2024-06-10 21:55:03 +02:00
|
|
|
Free()
|
2024-06-10 21:36:26 +02:00
|
|
|
return
|
|
|
|
}
|
2024-06-10 21:55:03 +02:00
|
|
|
f.Close()
|
2024-06-10 21:36:26 +02:00
|
|
|
// load images and make 64x64 thumbnails of them
|
|
|
|
thumbnail := imaging.Thumbnail(img, thumbnailSize, thumbnailSize, imaging.CatmullRom)
|
|
|
|
// create a new blank image
|
|
|
|
dst := imaging.New(thumbnailSize, thumbnailSize, color.NRGBA{0, 0, 0, 0})
|
|
|
|
// paste thumbnails into the new image side by side
|
|
|
|
dst = imaging.Paste(dst, thumbnail, image.Pt(0, 0))
|
|
|
|
// save the combined image to buffer
|
|
|
|
var buf bytes.Buffer
|
|
|
|
if FileTypesMap[fileExt] == imaging.PNG {
|
|
|
|
err = png.Encode(&buf, dst)
|
|
|
|
} else {
|
|
|
|
err = imaging.Encode(&buf, dst, FileTypesMap[fileExt])
|
|
|
|
}
|
|
|
|
if err != nil {
|
2024-06-10 21:55:03 +02:00
|
|
|
Free()
|
2024-06-10 21:36:26 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
thumbnailCacheMutex.Lock()
|
|
|
|
thumbnailCache[completePath] = buf.Bytes()
|
|
|
|
thumbnailCacheMutex.Unlock()
|
|
|
|
c.Write(buf.Bytes())
|
2024-06-10 21:55:03 +02:00
|
|
|
Free()
|
2024-06-10 21:36:26 +02:00
|
|
|
}
|