@@ -4,22 +4,17 @@ import (
44 "bytes"
55 "context"
66 "fmt"
7- "image"
8- "image/jpeg"
9- "image/png"
107 "io"
118 "mime/multipart"
129 "net/http"
1310 "path/filepath"
1411 "strconv"
1512 "strings"
1613
17- "github.com/chai2010/webp "
14+ "gopkg.in/h2non/bimg.v1 "
1815
1916 "mediaflow/internal/config"
2017 "mediaflow/internal/s3"
21-
22- "github.com/disintegration/imaging"
2318)
2419
2520type ImageService struct {
@@ -49,71 +44,114 @@ func NewImageService(cfg *config.Config) *ImageService {
4944func (s * ImageService ) UploadImage (ctx context.Context , so * config.StorageOptions , imageData []byte , thumbType , imagePath string ) error {
5045 orig_path := fmt .Sprintf ("%s/%s" , so .OriginFolder , imagePath )
5146 convertType := so .ConvertTo
52- // Upload original image
53- err := s .S3Client .PutObject (ctx , orig_path , bytes .NewReader (imageData ))
54- if err != nil {
55- return fmt .Errorf ("failed to upload original image to S3: %w" , err )
56- }
57-
58- // Generate and upload thumbnails for each size
59- for _ , sizeStr := range so .Sizes {
60- size , err := strconv .Atoi (sizeStr )
47+
48+ // Upload original image in parallel with thumbnail generation
49+ origUploadChan := make (chan error , 1 )
50+ go func () {
51+ err := s .S3Client .PutObject (ctx , orig_path , bytes .NewReader (imageData ))
6152 if err != nil {
62- return fmt .Errorf ("invalid size format: %s" , sizeStr )
53+ origUploadChan <- fmt .Errorf ("failed to upload original image to S3: %w" , err )
54+ } else {
55+ origUploadChan <- nil
6356 }
57+ }()
58+
59+ // Generate and upload thumbnails in parallel
60+ type thumbnailJob struct {
61+ sizeStr string
62+ data []byte
63+ path string
64+ err error
65+ }
66+
67+ thumbJobs := make (chan thumbnailJob , len (so .Sizes ))
68+ uploadErrors := make (chan error , len (so .Sizes ))
69+
70+ // Generate thumbnails in parallel
71+ for _ , sizeStr := range so .Sizes {
72+ go func (size string ) {
73+ sizeInt , err := strconv .Atoi (size )
74+ if err != nil {
75+ thumbJobs <- thumbnailJob {sizeStr : size , err : fmt .Errorf ("invalid size format: %s" , size )}
76+ return
77+ }
6478
65- // Generate thumbnail
66- thumbnailData , err := s . generateThumbnail ( imageData , size , so . Quality , convertType )
67- if err != nil {
68- return fmt . Errorf ( "failed to generate thumbnail for size %d: %w" , size , err )
69- }
79+ thumbnailData , err := s . generateThumbnail ( imageData , sizeInt , so . Quality , convertType )
80+ if err != nil {
81+ thumbJobs <- thumbnailJob { sizeStr : size , err : fmt . Errorf ( "failed to generate thumbnail for size %s: %w" , size , err )}
82+ return
83+ }
7084
71- // Create thumbnail path with size suffix
72- thumbSizePath := s .createThumbnailPathForSize (imagePath , sizeStr , convertType )
73- thumbFullPath := fmt .Sprintf ("%s/%s" , so .ThumbFolder , thumbSizePath )
85+ thumbSizePath := s .createThumbnailPathForSize (imagePath , size , convertType )
86+ thumbFullPath := fmt .Sprintf ("%s/%s" , so .ThumbFolder , thumbSizePath )
87+
88+ thumbJobs <- thumbnailJob {
89+ sizeStr : size ,
90+ data : thumbnailData ,
91+ path : thumbFullPath ,
92+ err : nil ,
93+ }
94+ }(sizeStr )
95+ }
7496
75- // Upload thumbnail
76- err = s .S3Client .PutObject (ctx , thumbFullPath , bytes .NewReader (thumbnailData ))
77- if err != nil {
78- return fmt .Errorf ("failed to upload thumbnail for size %d: %w" , size , err )
97+ // Upload thumbnails in parallel as they're generated
98+ for i := 0 ; i < len (so .Sizes ); i ++ {
99+ go func () {
100+ job := <- thumbJobs
101+ if job .err != nil {
102+ uploadErrors <- job .err
103+ return
104+ }
105+
106+ err := s .S3Client .PutObject (ctx , job .path , bytes .NewReader (job .data ))
107+ if err != nil {
108+ uploadErrors <- fmt .Errorf ("failed to upload thumbnail for size %s: %w" , job .sizeStr , err )
109+ } else {
110+ uploadErrors <- nil
111+ }
112+ }()
113+ }
114+
115+ // Wait for original upload
116+ if err := <- origUploadChan ; err != nil {
117+ return err
118+ }
119+
120+ // Wait for all thumbnail uploads
121+ for i := 0 ; i < len (so .Sizes ); i ++ {
122+ if err := <- uploadErrors ; err != nil {
123+ return err
79124 }
80125 }
81126
82127 return nil
83128}
84129
85130func (s * ImageService ) generateThumbnail (imageData []byte , width , quality int , convertTo string ) ([]byte , error ) {
86- // Decode the original image
87- img , _ , err := image .Decode (bytes .NewReader (imageData ))
88- if err != nil {
89- return nil , fmt .Errorf ("failed to decode image: %w" , err )
131+ options := bimg.Options {
132+ Width : width ,
133+ Quality : quality ,
90134 }
91-
92- resizedImg := imaging .Resize (img , width , 0 , imaging .Lanczos )
93-
94- // Encode the resized image
95- var buf bytes.Buffer
96-
97- // Determine content type and encode accordingly
98- if strings .Contains (convertTo , "jpeg" ) || strings .Contains (convertTo , "jpg" ) {
99- opts := & jpeg.Options {Quality : quality }
100- err = jpeg .Encode (& buf , resizedImg , opts )
101- } else if strings .Contains (convertTo , "png" ) {
102- err = png .Encode (& buf , resizedImg )
103- } else if strings .Contains (convertTo , "webp" ) {
104- opts := & webp.Options {Quality : float32 (quality )}
105- err = webp .Encode (& buf , resizedImg , opts )
106- } else {
135+
136+ // Set output format
137+ switch convertTo {
138+ case "webp" :
139+ options .Type = bimg .WEBP
140+ case "jpeg" , "jpg" :
141+ options .Type = bimg .JPEG
142+ case "png" :
143+ options .Type = bimg .PNG
144+ default :
107145 // Default to JPEG if format is unknown (fallback)
108- opts := & jpeg.Options {Quality : quality }
109- err = jpeg .Encode (& buf , resizedImg , opts )
146+ options .Type = bimg .JPEG
110147 }
111-
148+
149+ resizedData , err := bimg .NewImage (imageData ).Process (options )
112150 if err != nil {
113- return nil , fmt .Errorf ("failed to encode thumbnail : %w" , err )
151+ return nil , fmt .Errorf ("failed to process image with bimg : %w" , err )
114152 }
115-
116- return buf . Bytes () , nil
153+
154+ return resizedData , nil
117155}
118156
119157func (s * ImageService ) createThumbnailPathForSize (originalPath , size , newType string ) string {
0 commit comments