infoalloggi-monorepo/apps/backend/images.go
2026-05-22 15:49:28 +02:00

278 lines
6.9 KiB
Go

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
}