From 0932a10fedd5b63d522e33376112384ce0ae9a96 Mon Sep 17 00:00:00 2001 From: HFO4 <912394456@qq.com> Date: Sun, 8 Dec 2019 14:16:01 +0800 Subject: [PATCH] Feat: generate thumbnails for image file --- go.mod | 1 + models/file.go | 5 +++ pkg/filesystem/hooks.go | 2 - pkg/filesystem/image.go | 59 +++++++++++++++++++++--- pkg/filesystem/local/handler.go | 3 ++ pkg/filesystem/validator.go | 7 ++- pkg/thumb/image.go | 79 +++++++++++++++++++++++++++++++++ 7 files changed, 147 insertions(+), 9 deletions(-) create mode 100644 pkg/thumb/image.go diff --git a/go.mod b/go.mod index fab85f9..7314e54 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( github.com/mattn/go-colorable v0.1.4 // indirect github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2 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/qiniu/api.v7/v7 v7.4.0 github.com/smartystreets/goconvey v1.6.4 // indirect diff --git a/models/file.go b/models/file.go index 67777ed..3904a52 100644 --- a/models/file.go +++ b/models/file.go @@ -138,3 +138,8 @@ func GetFilesByParentIDs(ids []uint, uid uint) ([]File, error) { func (file *File) Rename(new string) 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 +} diff --git a/pkg/filesystem/hooks.go b/pkg/filesystem/hooks.go index 4dc6d83..833ba6f 100644 --- a/pkg/filesystem/hooks.go +++ b/pkg/filesystem/hooks.go @@ -130,8 +130,6 @@ func GenericAfterUpload(ctx context.Context, fs *FileSystem) error { return ErrInsertFileRecord } - // TODO 是否需要立即获取图像大小? - // 异步尝试生成缩略图 go fs.GenerateThumbnail(ctx, file) diff --git a/pkg/filesystem/image.go b/pkg/filesystem/image.go index 412784f..75290c1 100644 --- a/pkg/filesystem/image.go +++ b/pkg/filesystem/image.go @@ -2,18 +2,65 @@ package filesystem import ( "context" + "fmt" model "github.com/HFO4/cloudreve/models" + "github.com/HFO4/cloudreve/pkg/thumb" + "github.com/HFO4/cloudreve/pkg/util" ) -/* =============== - 图像处理相关 - =============== +/* ================ + 图像处理相关 + ================ */ // HandledExtension 可以生成缩略图的文件扩展名 -var HandledExtension = []string{} +var HandledExtension = []string{"jpg", "jpeg", "png", "gif"} -// GenerateThumbnail 尝试为文件生成缩略图 +// GenerateThumbnail 尝试为本地策略文件生成缩略图并获取图像原始大小 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 } diff --git a/pkg/filesystem/local/handler.go b/pkg/filesystem/local/handler.go index 23757f4..efa1362 100644 --- a/pkg/filesystem/local/handler.go +++ b/pkg/filesystem/local/handler.go @@ -75,6 +75,9 @@ func (handler Handler) Delete(ctx context.Context, files []string) ([]string, er retErr = err deleteFailed = append(deleteFailed, value) } + + // 尝试删除文件的缩略图(如果有) + _ = os.Remove(value + "._thumb") } return deleteFailed, retErr diff --git a/pkg/filesystem/validator.go b/pkg/filesystem/validator.go index b220b81..c87492e 100644 --- a/pkg/filesystem/validator.go +++ b/pkg/filesystem/validator.go @@ -57,13 +57,18 @@ func (fs *FileSystem) ValidateExtension(ctx context.Context, fileName string) bo return true } + return IsInExtensionList(fs.User.Policy.OptionsSerialized.FileType, fileName) +} + +// IsInExtensionList 返回文件的扩展名是否在给定的列表范围内 +func IsInExtensionList(extList []string, fileName string) bool { ext := strings.ToLower(filepath.Ext(fileName)) // 无扩展名时 if len(ext) == 0 { return false } - if util.ContainsString(fs.User.Policy.OptionsSerialized.FileType, ext[1:]) { + if util.ContainsString(extList, ext[1:]) { return true } diff --git a/pkg/thumb/image.go b/pkg/thumb/image.go new file mode 100644 index 0000000..1545a49 --- /dev/null +++ b/pkg/thumb/image.go @@ -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 + +}