package main import ( "bytes" "context" "fmt" "log" "path" "path/filepath" "strings" "github.com/h2non/bimg" "github.com/minio/minio-go/v7" ) func ProcessImage(ctx context.Context, client *minio.Client, bucket, key string) error { stat, err := client.StatObject(ctx, bucket, key, minio.StatObjectOptions{}) if err != nil { log.Printf("🚨 Failed to stat object: %s, error: %v", key, err) return err } if !strings.HasPrefix(stat.ContentType, "image/") { log.Printf("⏭️ Skipping non-image file: %s", key) return nil } if stat.UserMetadata["Processed"] == "true" || stat.UserMetadata["X-Amz-Meta-Processed"] == "true" { log.Printf("⏭️ Skipping already processed image: %s", key) return nil } object, err := client.GetObject(ctx, bucket, key, minio.GetObjectOptions{}) if err != nil { log.Printf("🚨 Failed to get object: %s, error: %v", key, err) return err } defer object.Close() sourceBuffer := new(bytes.Buffer) if _, err := sourceBuffer.ReadFrom(object); err != nil { log.Printf("🚨 Failed to read object: %s, error: %v", key, err) return err } img := bimg.NewImage(sourceBuffer.Bytes()) fileName := filepath.Base(key) filePath := path.Dir(key) meta := map[string]string{ "Processed": "true", } if err := generateAndUploadJPEG(ctx, client, img, bucket, filePath, fileName, meta); err != nil { return err } if err := generateAndUploadWebP(ctx, client, img, bucket, filePath, fileName, meta); err != nil { return err } if err := generateAndUploadThumbnail(ctx, client, img, bucket, filePath, fileName, meta); err != nil { return err } if err := UploadToMinIO(ctx, client, bucket, key, stat.ContentType, sourceBuffer.Bytes(), meta); err != nil { return fmt.Errorf("failed to reupload original image: %w", err) } log.Printf("✅ Image processed: %s", key) return nil } func generateAndUploadJPEG(ctx context.Context, client *minio.Client, img *bimg.Image, bucket, filePath, fileName string, meta map[string]string) error { jpegBuf, err := img.Process(bimg.Options{ Quality: 90, Type: bimg.JPEG, StripMetadata: true, }) if err != nil { return fmt.Errorf("failed to generate optimized JPEG: %w", err) } return UploadToMinIO(ctx, client, bucket, path.Join(filePath, "optimized", fileName), "image/jpeg", jpegBuf, meta) } func generateAndUploadWebP(ctx context.Context, client *minio.Client, img *bimg.Image, bucket, filePath, fileName string, meta map[string]string) error { webpBuf, err := img.Process(bimg.Options{ Quality: 90, Type: bimg.WEBP, StripMetadata: true, }) if err != nil { return fmt.Errorf("failed to generate WebP: %w", err) } webpName := strings.TrimSuffix(fileName, filepath.Ext(fileName)) + ".webp" return UploadToMinIO(ctx, client, bucket, path.Join(filePath, "webp", webpName), "image/webp", webpBuf, meta) } func generateAndUploadThumbnail(ctx context.Context, client *minio.Client, img *bimg.Image, bucket, filePath, fileName string, meta map[string]string) error { thumbBuf, err := img.Process(bimg.Options{ Width: 400, Quality: 85, Type: bimg.JPEG, StripMetadata: true, }) if err != nil { return fmt.Errorf("failed to generate thumbnail: %w", err) } return UploadToMinIO(ctx, client, bucket, path.Join(filePath, "thumbs", fileName), "image/jpeg", thumbBuf, meta) }