package main import ( "backend/config" models "backend/gen/postgres/postgres/public/model" "backend/queries" "backend/typesdefs" "backend/utils" "fmt" "image" "image/jpeg" "image/png" "io" "log" "net/http" "os" "path/filepath" "strings" "sync" "time" "github.com/nfnt/resize" "github.com/nickalie/go-webpbin" ) func AddToImageProcessing(tmp *typesdefs.AnnuncioParsed, annuncio *typesdefs.AnnuncioXML, p *[]typesdefs.MediaToProcess) { if annuncio.Foto == nil || len(*annuncio.Foto) == 0 { return } for idx, url := range *annuncio.Foto { *p = append(*p, typesdefs.MediaToProcess{ Codice: tmp.Codice, Url: url, Order: idx, }) } } func ProcessImagePool(toProcess *[]typesdefs.MediaToProcess, isDryRun bool) error { if len(*toProcess) == 0 { log.Println("No new images to process.") return nil } var wg sync.WaitGroup sem := make(chan struct{}, config.Cfg.Images.ConcurrentLimit) totalImages := len(*toProcess) log.Printf("Starting processing of %d images with concurrency limit %d...", totalImages, config.Cfg.Images.ConcurrentLimit) // clear images folder if isDryRun { log.Println("Dry run. Clearing images folder...") } else { err := clearImageFolder() if err != nil { log.Fatalf("Failed to initialize image processing: %v", err) } } // process images for _, img := range *toProcess { sem <- struct{}{} // acquire semaphore wg.Add(1) go func(img typesdefs.MediaToProcess) { defer wg.Done() defer func() { <-sem }() // release semaphore if isDryRun { log.Printf("Dry run: image processing: %s_%d", img.Codice, img.Order) return } log.Printf("%s_%d: elaborating", img.Codice, img.Order) imgPath := filepath.Join(config.Cfg.Images.Path, fmt.Sprintf("%s_%d%s", img.Codice, img.Order, filepath.Ext(img.Url))) log.Printf("%s_%d: downloading", img.Codice, img.Order) err := downloadImage(img.Url, imgPath) if err != nil { log.Printf("Failed to download image %s: %v", img.Url, err) return // Exit goroutine on download failure } log.Printf("%s_%d: conversion", img.Codice, img.Order) webpPath, thumbnailwebpPath, err := Conversion(imgPath) if err != nil { log.Fatalf("Failed to convert image: %v", err) } log.Printf("%s_%d: upload", img.Codice, img.Order) imgId, err := queries.UploadFile(webpPath, "image/webp", "images") if err != nil { log.Printf("failed to upload image: %v", err) return } thumbId, err := queries.UploadFile(thumbnailwebpPath, "image/webp", "images") if err != nil { log.Printf("failed to upload thumbnail: %v", err) return } log.Printf("%s_%d: images_refs upd", img.Codice, img.Order) err = queries.SetMediaToDB(models.MediaRefs{ Codice: img.Codice, Ordine: int32(img.Order), OgURL: img.Url, MediaType: "image", StorageID: imgId, IsThumbnail: false, }) if err != nil { log.Printf("failed to set media to db: %v", err) return } err = queries.SetMediaToDB(models.MediaRefs{ Codice: img.Codice, Ordine: int32(img.Order), OgURL: img.Url, MediaType: "image", StorageID: thumbId, IsThumbnail: true, }) if err != nil { log.Printf("failed to set media to db: %v", err) return } log.Printf("%s_%d: done", img.Codice, img.Order) }(img) } wg.Wait() log.Printf("Processed all images") fmt.Println() return nil } func clearImageFolder() error { err := utils.SafeRemoveAll(config.Cfg.Images.Path) if err != nil { return err } err = os.Mkdir(config.Cfg.Images.Path, 0755) if err != nil { return err } return nil } func downloadImage(url, filename string) error { client := &http.Client{ Timeout: 2 * time.Minute, // Adjust based on expected image sizes } response, err := client.Get(url) if err != nil { return fmt.Errorf("failed to download image: %w", err) } defer response.Body.Close() // Check HTTP status if response.StatusCode != http.StatusOK { return fmt.Errorf("failed to download image: HTTP %d", response.StatusCode) } // Optional: Validate Content-Type contentType := response.Header.Get("Content-Type") if !strings.HasPrefix(contentType, "image/") { log.Printf("Warning: unexpected content type %s for %s", contentType, url) } file, err := os.Create(filename) if err != nil { return fmt.Errorf("failed to create image file: %w", err) } defer file.Close() _, err = io.Copy(file, response.Body) if err != nil { return fmt.Errorf("failed to save image file: %w", err) } return nil } func Conversion(input string) (string, string, error) { var empty string var width uint = 1920 output := filepath.Base(input) outputdir := filepath.Dir(input) ext := filepath.Ext(output) output = output[0 : len(output)-len(ext)] // Remove file extension // Add .webp extension //log.Println("Converting", input, "to", output+".webp") f, err := os.Open(input) if err != nil { return empty, empty, err } defer f.Close() var img image.Image switch ext { case ".webp": log.Println("Image already in WebP format") img, err = webpbin.Decode(f) if err != nil { return empty, empty, err } case ".png", ".PNG": // Convert from PNG img, err = png.Decode(f) if err != nil { return empty, empty, err } case ".jpeg", ".jpg", ".JPG", ".JPEG": // Convert from JPEG img, err = jpeg.Decode(f) if err != nil { return empty, empty, err } default: return empty, empty, fmt.Errorf("error: Unsupported file type: %s", ext) } err = image_creation(img, width, output, outputdir) if err != nil { return empty, empty, fmt.Errorf("error in image creation: %w", err) } err = thumbnail_creation(img, output, outputdir) if err != nil { return empty, empty, fmt.Errorf("error in thumbnail creation: %w", err) } //fmt.Println("Conversion completed successfully") return fmt.Sprintf("%s/%s.webp", outputdir, output), fmt.Sprintf("%s/thumbnail-%s.webp", outputdir, output), nil } func image_creation(img image.Image, width uint, output string, outputdir string) error { // Resize the image resizedImg := resize.Resize(width, 0, img, resize.Lanczos3) // Create the output file outputPath := fmt.Sprintf("%s/%s.webp", outputdir, output) //log.Println("Creating", outputPath) out, err := os.Create(outputPath) if err != nil { return err } defer out.Close() // Encode the resized image to the output file err = webpbin.Encode(out, resizedImg) if err != nil { return err } return nil } func thumbnail_creation(img image.Image, output string, outputdir string) error { // Resize the image resizedImg := resize.Resize(32, 0, img, resize.Lanczos3) // Create the output file outputPath := fmt.Sprintf("%s/thumbnail-%s.webp", outputdir, output) //log.Println("Creating", outputPath) out, err := os.Create(outputPath) if err != nil { return err } defer out.Close() // Encode the resized image to the output file err = webpbin.Encode(out, resizedImg) if err != nil { return err } return nil }