/
thumbnail.go
145 lines (128 loc) · 4.15 KB
/
thumbnail.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
package imaging
import (
"bytes"
"github.com/comiccruncher/comiccruncher/internal/log"
"github.com/comiccruncher/comiccruncher/storage"
"github.com/disintegration/imaging"
"go.uber.org/zap"
"image"
"image/jpeg"
"io"
"io/ioutil"
"time"
)
// Width defines the width of the thumbnail.
type Width int
// Height defines the height of the thumbnail.
type Height int
// Thumbnailer is the interface for generating thumbnails.
type Thumbnailer interface {
Resize(body io.Reader, width, height int) (*bytes.Buffer, error)
}
// ThumbnailUploader is for uploading generating and uploading thumbnails.
type ThumbnailUploader interface {
Generate(key string, opts *ThumbnailOptions) ([]*ThumbnailResult, error)
}
// ThumbnailResult is the resulting thumbnail generated.
type ThumbnailResult struct {
Pathname string
Dimensions *ThumbnailSize
}
// ThumbnailSize defines the width and height of the thumbnails.
type ThumbnailSize struct {
Width Width
Height Height
}
// ThumbnailOptions defines options for generating thumbnails.
type ThumbnailOptions struct {
Sizes []*ThumbnailSize // The widths and heights for the thumbnails.
NamingStrategy storage.FileNameStrategy // The strategy for generating thumbnail filenames.
RemoteDir string // The remote directory to upload the thumbnails.
}
// InMemoryThumbnailer is for resizing images to thumbnails in memory (doesn't save to temp file, etc).
type InMemoryThumbnailer struct{}
// Resize resizes the image to the given width and height.
func (t *InMemoryThumbnailer) Resize(body io.Reader, width, height int) (*bytes.Buffer, error) {
src, _, err := image.Decode(body)
if err != nil {
return nil, err
}
img := imaging.Resize(src, width, height, imaging.Box)
buf := new(bytes.Buffer)
err = jpeg.Encode(buf, img, nil)
return buf, err
}
// S3ThumbnailUploader is for generating and uploading thumbnails to the remote storage.
type S3ThumbnailUploader struct {
s storage.Storage
thumbnailer Thumbnailer
}
// Generate generates thumbnails for the given key stored in S3.
func (u *S3ThumbnailUploader) Generate(key string, opts *ThumbnailOptions) ([]*ThumbnailResult, error) {
rdr, err := u.s.Download(key)
defer closeReader(rdr)
if err != nil {
return nil, err
}
sizes := opts.Sizes
remoteDir := opts.RemoteDir
strat := opts.NamingStrategy
results := make([]*ThumbnailResult, len(sizes))
for i, size := range sizes {
// reset seeker so it gets read again
if i != 0 {
_, err = rdr.Seek(0, 0)
if err != nil {
return nil, err
}
}
buf, err := u.thumbnailer.Resize(rdr, int(size.Width), int(size.Height))
if err != nil {
closeReader(buf)
return nil, err
}
filename := remoteDir + strat(string(time.Now().Nanosecond())+".jpg")
err = u.s.UploadBytes(buf, filename)
if err != nil {
closeReader(buf)
return nil, err
}
closeReader(buf)
results[i] = &ThumbnailResult{Dimensions: size, Pathname: filename}
log.IMAGING().Info("uploaded thumbnail", zap.String("pathname", filename))
}
return results, nil
}
func closeReader(r io.Reader) {
if r != nil {
ioutil.NopCloser(r).Close()
}
}
// NewInMemoryThumbnailer returns a new image thumbnailer.
func NewInMemoryThumbnailer() *InMemoryThumbnailer {
return &InMemoryThumbnailer{}
}
// NewS3ThumbnailUploader creates a new s3 thumbnail uploader.
func NewS3ThumbnailUploader(s storage.Storage, thumbnailer Thumbnailer) *S3ThumbnailUploader {
return &S3ThumbnailUploader{
s: s,
thumbnailer: thumbnailer,
}
}
// NewDefaultThumbnailOptions conveniently creates thumbnail options from the parameters
// with the crc32 time naming strategy as the default filename strat.
func NewDefaultThumbnailOptions(remoteDir string, sizes ...*ThumbnailSize) *ThumbnailOptions {
szs := make([]*ThumbnailSize, len(sizes))
for i, size := range sizes {
szs[i] = size
}
return &ThumbnailOptions{
Sizes: szs,
NamingStrategy: storage.Crc32TimeNamingStrategy(),
RemoteDir: remoteDir,
}
}
// NewThumbnailSize creates a new thumbnail size for the given width and height.
func NewThumbnailSize(width, height int) *ThumbnailSize {
return &ThumbnailSize{Width: Width(width), Height: Height(height)}
}