mirror of https://github.com/cloudreve/Cloudreve
Feat: generate thumbnails for image file
parent
a445da5286
commit
0932a10fed
1
go.mod
1
go.mod
|
@ -16,6 +16,7 @@ require (
|
||||||
github.com/mattn/go-colorable v0.1.4 // indirect
|
github.com/mattn/go-colorable v0.1.4 // indirect
|
||||||
github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2
|
github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2
|
||||||
github.com/mojocn/base64Captcha v0.0.0-20190801020520-752b1cd608b2
|
github.com/mojocn/base64Captcha v0.0.0-20190801020520-752b1cd608b2
|
||||||
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
|
||||||
github.com/pkg/errors v0.8.0
|
github.com/pkg/errors v0.8.0
|
||||||
github.com/qiniu/api.v7/v7 v7.4.0
|
github.com/qiniu/api.v7/v7 v7.4.0
|
||||||
github.com/smartystreets/goconvey v1.6.4 // indirect
|
github.com/smartystreets/goconvey v1.6.4 // indirect
|
||||||
|
|
|
@ -138,3 +138,8 @@ func GetFilesByParentIDs(ids []uint, uid uint) ([]File, error) {
|
||||||
func (file *File) Rename(new string) error {
|
func (file *File) Rename(new string) error {
|
||||||
return DB.Model(&file).Update("name", new).Error
|
return DB.Model(&file).Update("name", new).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdatePicInfo 更新文件的图像信息
|
||||||
|
func (file *File) UpdatePicInfo(value string) error {
|
||||||
|
return DB.Model(&file).Update("pic_info", value).Error
|
||||||
|
}
|
||||||
|
|
|
@ -130,8 +130,6 @@ func GenericAfterUpload(ctx context.Context, fs *FileSystem) error {
|
||||||
return ErrInsertFileRecord
|
return ErrInsertFileRecord
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO 是否需要立即获取图像大小?
|
|
||||||
|
|
||||||
// 异步尝试生成缩略图
|
// 异步尝试生成缩略图
|
||||||
go fs.GenerateThumbnail(ctx, file)
|
go fs.GenerateThumbnail(ctx, file)
|
||||||
|
|
||||||
|
|
|
@ -2,18 +2,65 @@ package filesystem
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
model "github.com/HFO4/cloudreve/models"
|
model "github.com/HFO4/cloudreve/models"
|
||||||
|
"github.com/HFO4/cloudreve/pkg/thumb"
|
||||||
|
"github.com/HFO4/cloudreve/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
/* ===============
|
/* ================
|
||||||
图像处理相关
|
图像处理相关
|
||||||
===============
|
================
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// HandledExtension 可以生成缩略图的文件扩展名
|
// HandledExtension 可以生成缩略图的文件扩展名
|
||||||
var HandledExtension = []string{}
|
var HandledExtension = []string{"jpg", "jpeg", "png", "gif"}
|
||||||
|
|
||||||
// GenerateThumbnail 尝试为文件生成缩略图
|
// GenerateThumbnail 尝试为本地策略文件生成缩略图并获取图像原始大小
|
||||||
func (fs *FileSystem) GenerateThumbnail(ctx context.Context, file *model.File) {
|
func (fs *FileSystem) GenerateThumbnail(ctx context.Context, file *model.File) {
|
||||||
// TODO
|
// 判断是否可以生成缩略图
|
||||||
|
if !IsInExtensionList(HandledExtension, file.Name) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新建上下文
|
||||||
|
newCtx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// 获取文件数据
|
||||||
|
source, err := fs.Handler.Get(newCtx, file.SourceName)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
image, err := thumb.NewThumbFromFile(source, file.Name)
|
||||||
|
if err != nil {
|
||||||
|
util.Log().Warning("生成缩略图时无法解析[%s]图像数据:%s", file.SourceName, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取原始图像尺寸
|
||||||
|
w, h := image.GetSize()
|
||||||
|
|
||||||
|
// 生成缩略图
|
||||||
|
image.GetThumb(fs.GenerateThumbnailSize(w, h))
|
||||||
|
// 保存到文件
|
||||||
|
err = image.Save(file.SourceName + "._thumb")
|
||||||
|
if err != nil {
|
||||||
|
util.Log().Warning("无法保存缩略图:%s", file.SourceName, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新文件的图像信息
|
||||||
|
err = file.UpdatePicInfo(fmt.Sprintf("%d,%d", w, h))
|
||||||
|
|
||||||
|
// 失败时删除缩略图文件
|
||||||
|
if err != nil {
|
||||||
|
_, _ = fs.Handler.Delete(newCtx, []string{file.SourceName + "._thumb"})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateThumbnailSize 获取要生成的缩略图的尺寸
|
||||||
|
func (fs *FileSystem) GenerateThumbnailSize(w, h int) (uint, uint) {
|
||||||
|
return 230, 200
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,6 +75,9 @@ func (handler Handler) Delete(ctx context.Context, files []string) ([]string, er
|
||||||
retErr = err
|
retErr = err
|
||||||
deleteFailed = append(deleteFailed, value)
|
deleteFailed = append(deleteFailed, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 尝试删除文件的缩略图(如果有)
|
||||||
|
_ = os.Remove(value + "._thumb")
|
||||||
}
|
}
|
||||||
|
|
||||||
return deleteFailed, retErr
|
return deleteFailed, retErr
|
||||||
|
|
|
@ -57,13 +57,18 @@ func (fs *FileSystem) ValidateExtension(ctx context.Context, fileName string) bo
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return IsInExtensionList(fs.User.Policy.OptionsSerialized.FileType, fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsInExtensionList 返回文件的扩展名是否在给定的列表范围内
|
||||||
|
func IsInExtensionList(extList []string, fileName string) bool {
|
||||||
ext := strings.ToLower(filepath.Ext(fileName))
|
ext := strings.ToLower(filepath.Ext(fileName))
|
||||||
// 无扩展名时
|
// 无扩展名时
|
||||||
if len(ext) == 0 {
|
if len(ext) == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if util.ContainsString(fs.User.Policy.OptionsSerialized.FileType, ext[1:]) {
|
if util.ContainsString(extList, ext[1:]) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
package thumb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"image"
|
||||||
|
"image/gif"
|
||||||
|
"image/jpeg"
|
||||||
|
"image/png"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/nfnt/resize"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Thumb 缩略图
|
||||||
|
type Thumb struct {
|
||||||
|
src image.Image
|
||||||
|
ext string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewThumbFromFile 从文件数据获取新的Thumb对象,
|
||||||
|
// 尝试通过文件名name解码图像
|
||||||
|
func NewThumbFromFile(file io.ReadSeeker, name string) (*Thumb, error) {
|
||||||
|
ext := strings.ToLower(filepath.Ext(name))
|
||||||
|
// 无扩展名时
|
||||||
|
if len(ext) == 0 {
|
||||||
|
return nil, errors.New("未知的图像类型")
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var img image.Image
|
||||||
|
switch ext[1:] {
|
||||||
|
case "jpg":
|
||||||
|
img, err = jpeg.Decode(file)
|
||||||
|
case "jpeg":
|
||||||
|
img, err = jpeg.Decode(file)
|
||||||
|
case "gif":
|
||||||
|
img, err = gif.Decode(file)
|
||||||
|
case "png":
|
||||||
|
img, err = png.Decode(file)
|
||||||
|
default:
|
||||||
|
return nil, errors.New("未知的图像类型")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Thumb{
|
||||||
|
src: img,
|
||||||
|
ext: ext[1:],
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetThumb 生成给定最大尺寸的缩略图
|
||||||
|
func (image *Thumb) GetThumb(width, height uint) {
|
||||||
|
image.src = resize.Thumbnail(width, height, image.src, resize.Lanczos3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSize 获取图像尺寸
|
||||||
|
func (image *Thumb) GetSize() (int, int) {
|
||||||
|
b := image.src.Bounds()
|
||||||
|
return b.Max.X, b.Max.Y
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save 保存图像到给定路径
|
||||||
|
func (image *Thumb) Save(path string) (err error) {
|
||||||
|
out, err := os.Create(path)
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = jpeg.Encode(out, image.src, nil)
|
||||||
|
return err
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue