feat: Enhance image processing in data-fetching and update SEO meta tags for product images; add Telegram assistant link in ChatAssistant component with localization support
This commit is contained in:
@@ -186,80 +186,98 @@ const saveProductImages = async (socket, products, categoryName, outputDir) => {
|
||||
.filter((id) => id);
|
||||
|
||||
if (imageIds.length > 0) {
|
||||
// Process first image for each product
|
||||
// Process first image for each product — store AVIF + JPEG (e.g. for Twitter / social)
|
||||
const bildId = parseInt(imageIds[0]);
|
||||
const estimatedFilename = `prod${bildId}.avif`; // We'll generate a filename based on the ID
|
||||
const avifFilename = `prod${bildId}.avif`;
|
||||
const jpegFilename = `prod${bildId}.jpg`;
|
||||
const avifPath = path.join(assetsPath, avifFilename);
|
||||
const jpegPath = path.join(assetsPath, jpegFilename);
|
||||
|
||||
const imagePath = path.join(assetsPath, estimatedFilename);
|
||||
|
||||
// Skip if image already exists
|
||||
if (fs.existsSync(imagePath)) {
|
||||
if (fs.existsSync(avifPath) && fs.existsSync(jpegPath)) {
|
||||
imagesSkipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const writeAvifAndJpegFromBuffer = async (buf) => {
|
||||
if (!fs.existsSync(avifPath)) {
|
||||
await sharp(buf).avif().toFile(avifPath);
|
||||
}
|
||||
if (!fs.existsSync(jpegPath)) {
|
||||
await sharp(buf)
|
||||
.jpeg({ quality: 85, mozjpeg: true })
|
||||
.toFile(jpegPath);
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const imageBuffer = await fetchProductImage(socket, bildId);
|
||||
|
||||
// If overlay exists, apply it to the image
|
||||
if (false && fs.existsSync(overlayPath)) {
|
||||
try {
|
||||
// Get image dimensions to center the overlay
|
||||
const baseImage = sharp(Buffer.from(imageBuffer));
|
||||
const baseMetadata = await baseImage.metadata();
|
||||
|
||||
const overlaySize = Math.min(baseMetadata.width, baseMetadata.height) * 0.4;
|
||||
|
||||
// Resize overlay to 20% of base image size and get its buffer
|
||||
const resizedOverlayBuffer = await sharp(overlayPath)
|
||||
.resize({
|
||||
width: Math.round(overlaySize),
|
||||
height: Math.round(overlaySize),
|
||||
fit: 'contain', // Keep full overlay visible
|
||||
background: { r: 0, g: 0, b: 0, alpha: 0 } // Transparent background instead of black bars
|
||||
})
|
||||
.toBuffer();
|
||||
|
||||
// Calculate center position for the resized overlay
|
||||
const centerX = Math.floor((baseMetadata.width - overlaySize) / 2);
|
||||
const centerY = Math.floor((baseMetadata.height - overlaySize) / 2);
|
||||
|
||||
const processedImageBuffer = await baseImage
|
||||
.composite([
|
||||
{
|
||||
input: resizedOverlayBuffer,
|
||||
top: centerY,
|
||||
left: centerX,
|
||||
blend: "multiply", // Darkens the image, visible on all backgrounds
|
||||
opacity: 0.3,
|
||||
},
|
||||
])
|
||||
.avif() // Ensure output is AVIF
|
||||
.toBuffer();
|
||||
|
||||
fs.writeFileSync(imagePath, processedImageBuffer);
|
||||
console.log(
|
||||
` ✅ Applied centered inverted sh.avif overlay to ${estimatedFilename}`
|
||||
);
|
||||
} catch (overlayError) {
|
||||
console.log(
|
||||
` ⚠️ Failed to apply overlay to ${estimatedFilename}: ${overlayError.message}`
|
||||
);
|
||||
// Fallback: save without overlay
|
||||
fs.writeFileSync(imagePath, Buffer.from(imageBuffer));
|
||||
}
|
||||
if (fs.existsSync(avifPath) && !fs.existsSync(jpegPath)) {
|
||||
await sharp(avifPath)
|
||||
.jpeg({ quality: 85, mozjpeg: true })
|
||||
.toFile(jpegPath);
|
||||
} else if (!fs.existsSync(avifPath) && fs.existsSync(jpegPath)) {
|
||||
await sharp(jpegPath).avif().toFile(avifPath);
|
||||
} else {
|
||||
// Save without overlay if overlay file doesn't exist
|
||||
fs.writeFileSync(imagePath, Buffer.from(imageBuffer));
|
||||
const imageBuffer = await fetchProductImage(socket, bildId);
|
||||
const buf = Buffer.from(imageBuffer);
|
||||
|
||||
// If overlay exists, apply it to the image
|
||||
if (false && fs.existsSync(overlayPath)) {
|
||||
try {
|
||||
const baseImage = sharp(buf);
|
||||
const baseMetadata = await baseImage.metadata();
|
||||
|
||||
const overlaySize =
|
||||
Math.min(baseMetadata.width, baseMetadata.height) * 0.4;
|
||||
|
||||
const resizedOverlayBuffer = await sharp(overlayPath)
|
||||
.resize({
|
||||
width: Math.round(overlaySize),
|
||||
height: Math.round(overlaySize),
|
||||
fit: "contain",
|
||||
background: { r: 0, g: 0, b: 0, alpha: 0 },
|
||||
})
|
||||
.toBuffer();
|
||||
|
||||
const centerX = Math.floor(
|
||||
(baseMetadata.width - overlaySize) / 2
|
||||
);
|
||||
const centerY = Math.floor(
|
||||
(baseMetadata.height - overlaySize) / 2
|
||||
);
|
||||
|
||||
const processedImageBuffer = await baseImage
|
||||
.composite([
|
||||
{
|
||||
input: resizedOverlayBuffer,
|
||||
top: centerY,
|
||||
left: centerX,
|
||||
blend: "multiply",
|
||||
opacity: 0.3,
|
||||
},
|
||||
])
|
||||
.toBuffer();
|
||||
|
||||
await writeAvifAndJpegFromBuffer(processedImageBuffer);
|
||||
console.log(
|
||||
` ✅ Applied overlay → ${avifFilename} + ${jpegFilename}`
|
||||
);
|
||||
} catch (overlayError) {
|
||||
console.log(
|
||||
` ⚠️ Failed to apply overlay to prod${bildId}: ${overlayError.message}`
|
||||
);
|
||||
await writeAvifAndJpegFromBuffer(buf);
|
||||
}
|
||||
} else {
|
||||
await writeAvifAndJpegFromBuffer(buf);
|
||||
}
|
||||
}
|
||||
|
||||
imagesSaved++;
|
||||
|
||||
// Small delay to avoid overwhelming server
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
} catch (error) {
|
||||
console.log(
|
||||
` ⚠️ Failed to fetch image ${estimatedFilename} (ID: ${bildId}): ${error.message}`
|
||||
` ⚠️ Failed to fetch/save prod${bildId} (${avifFilename} / ${jpegFilename}): ${error.message}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user