github.com/openimsdk/tools@v0.0.49/s3/minio/thumbnail.go (about) 1 // Copyright © 2023 OpenIM. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package minio 16 17 import ( 18 "bytes" 19 "context" 20 "fmt" 21 "golang.org/x/image/bmp" 22 "golang.org/x/image/tiff" 23 "image" 24 "image/gif" 25 "image/jpeg" 26 "image/png" 27 "net/url" 28 "path/filepath" 29 "strings" 30 "time" 31 32 "github.com/chai2010/webp" 33 "github.com/minio/minio-go/v7" 34 "github.com/openimsdk/tools/errs" 35 "github.com/openimsdk/tools/log" 36 "github.com/openimsdk/tools/s3" 37 ) 38 39 func (m *Minio) getImageThumbnailURL(ctx context.Context, name string, expire time.Duration, opt *s3.Image) (string, error) { 40 var img image.Image 41 info, err := m.cache.GetImageObjectKeyInfo(ctx, name, func(ctx context.Context) (info *ImageInfo, err error) { 42 info, img, err = m.getObjectImageInfo(ctx, name) 43 return 44 }) 45 if err != nil { 46 return "", err 47 } 48 if !info.IsImg { 49 return "", errs.New("object not image").Wrap() 50 } 51 if opt.Width > info.Width || opt.Width <= 0 { 52 opt.Width = info.Width 53 } 54 if opt.Height > info.Height || opt.Height <= 0 { 55 opt.Height = info.Height 56 } 57 opt.Format = strings.ToLower(opt.Format) 58 if opt.Format == formatJpg { 59 opt.Format = formatJpeg 60 } 61 switch opt.Format { 62 case formatPng, formatJpeg, formatGif: 63 default: 64 opt.Format = "" 65 } 66 reqParams := make(url.Values) 67 if opt.Width == info.Width && opt.Height == info.Height && (opt.Format == info.Format || opt.Format == "") { 68 reqParams.Set("response-content-type", "image/"+info.Format) 69 return m.PresignedGetObject(ctx, name, expire, reqParams) 70 } 71 if opt.Format == "" { 72 switch opt.Format { 73 case formatGif: 74 opt.Format = formatGif 75 case formatJpeg: 76 opt.Format = formatJpeg 77 case formatPng: 78 opt.Format = formatPng 79 default: 80 opt.Format = formatPng 81 } 82 } 83 key, err := m.cache.GetThumbnailKey(ctx, name, opt.Format, opt.Width, opt.Height, func(ctx context.Context) (string, error) { 84 if img == nil { 85 var reader *minio.Object 86 reader, err = m.core.Client.GetObject(ctx, m.bucket, name, minio.GetObjectOptions{}) 87 if err != nil { 88 return "", err 89 } 90 defer reader.Close() 91 img, _, err = ImageStat(reader) 92 if err != nil { 93 return "", err 94 } 95 } 96 thumbnail := resizeImage(img, opt.Width, opt.Height) 97 buf := bytes.NewBuffer(nil) 98 switch opt.Format { 99 case formatPng: 100 err = png.Encode(buf, thumbnail) 101 case formatJpeg: 102 err = jpeg.Encode(buf, thumbnail, nil) 103 case formatGif: 104 err = gif.Encode(buf, thumbnail, nil) 105 case formatWebP: 106 err = webp.Encode(buf, thumbnail, nil) 107 case formatTiff: 108 err = tiff.Encode(buf, thumbnail, nil) 109 case formatBmp: 110 err = bmp.Encode(buf, thumbnail) 111 case formatAvif: 112 //err = avif.Encode(buf, img, nil) 113 case formatHeic, formatHeif: 114 115 } 116 if err != nil { 117 return "", errs.WrapMsg(err, "encode failed", "type", opt.Format) 118 } 119 cacheKey := filepath.Join(imageThumbnailPath, info.Etag, fmt.Sprintf("image_w%d_h%d.%s", opt.Width, opt.Height, opt.Format)) 120 if _, err = m.core.Client.PutObject(ctx, m.bucket, cacheKey, buf, int64(buf.Len()), minio.PutObjectOptions{}); err != nil { 121 return "", err 122 } 123 return cacheKey, nil 124 }) 125 if err != nil { 126 return "", err 127 } 128 reqParams.Set("response-content-type", "image/"+opt.Format) 129 return m.PresignedGetObject(ctx, key, expire, reqParams) 130 } 131 132 func (m *Minio) getObjectImageInfo(ctx context.Context, name string) (*ImageInfo, image.Image, error) { 133 fileInfo, err := m.StatObject(ctx, name) 134 if err != nil { 135 return nil, nil, err 136 } 137 if fileInfo.Size > maxImageSize { 138 return nil, nil, errs.New("file size too large").Wrap() 139 } 140 imageData, err := m.getObjectData(ctx, name, fileInfo.Size) 141 if err != nil { 142 return nil, nil, err 143 } 144 var info ImageInfo 145 imageInfo, format, err := ImageStat(bytes.NewReader(imageData)) 146 if err == nil { 147 info.IsImg = true 148 info.Format = format 149 info.Width, info.Height = ImageWidthHeight(imageInfo) 150 } else { 151 info.IsImg = false 152 } 153 info.Etag = fileInfo.ETag 154 return &info, imageInfo, nil 155 } 156 157 func (m *Minio) delObjectImageInfoKey(ctx context.Context, key string, size int64) { 158 if size > 0 && size > maxImageSize { 159 return 160 } 161 if err := m.cache.DelObjectImageInfoKey(ctx, key); err != nil { 162 log.ZError(ctx, "DelObjectImageInfoKey failed", err, "key", key) 163 } 164 }