#!/usr/bin/env php 100) { $quality = 80; } if (!is_dir($storageRoot) || !is_readable($storageRoot)) { fwrite(STDERR, "[generate-cover-webp] storage root not readable: $storageRoot\n"); exit(1); } if (!function_exists('exec')) { fwrite(STDERR, "[generate-cover-webp] exec() is disabled; cannot run cwebp.\n"); exit(1); } // Recursively collect cover files. $covers = []; $rii = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($storageRoot, FilesystemIterator::SKIP_DOTS) ); foreach ($rii as $entry) { if (!$entry->isFile()) { continue; } $name = $entry->getFilename(); // Only source covers (original upload) count as inputs; the generated // `.webp` sidecars must NOT be re-scanned as sources. if (!preg_match('/_COUVERTURE\.[a-z0-9]+$/i', $name) || str_ends_with($name, '.webp')) { continue; } $covers[] = $entry->getPathname(); } sort($covers); $pending = []; $existing = 0; foreach ($covers as $sourcePath) { $webpPath = preg_replace('/\.[a-z0-9]+$/i', '.webp', $sourcePath); if ($webpPath !== null && is_file($webpPath)) { $existing++; continue; } $pending[] = $sourcePath; } $skipped = 0; if ($dryRun) { foreach ($pending as $p) { printf("DRY-RUN → %s (%d bytes)\n", $p, (int) filesize($p)); } printf( "Found %d cover file(s): %d already have a .webp thumbnail, %d pending.\n", count($covers), $existing, count($pending) ); echo "Re-run with --no-dry-run to generate them.\n"; exit(0); } foreach ($pending as $sourcePath) { $targetPath = preg_replace('/\.[a-z0-9]+$/i', '.webp', $sourcePath); $cmd = sprintf( 'cwebp -quiet -resize %d 0 -q %d %s -o %s 2>/dev/null', $maxWidth, $quality, escapeshellarg((string) $sourcePath), escapeshellarg((string) $targetPath) ); exec($cmd, $_, $code); $ok = $code === 0 && $targetPath !== null && is_file($targetPath); // cwebp can't read CMYK JPEGs; fall back to GD (php-gd) which handles // CMYK->RGB on decode, same resize + quality. if (!$ok && function_exists('imagecreatefromjpeg') && function_exists('imagewebp')) { $im = @imagecreatefromjpeg((string) $sourcePath); if ($im !== false && $targetPath !== null) { $srcW = imagesx($im); $srcH = imagesy($im); $dstW = min($maxWidth, $srcW); $dstH = (int) round($srcH * $dstW / $srcW); $tgt = imagecreatetruecolor($dstW, $dstH); imagecopyresampled($tgt, $im, 0, 0, 0, 0, $dstW, $dstH, $srcW, $srcH); $ok = imagewebp($tgt, $targetPath, $quality); imagedestroy($im); imagedestroy($tgt); if ($ok) { @chmod($targetPath, 0644); } } } if ($ok && $targetPath !== null && is_file($targetPath)) { @chmod($targetPath, 0644); printf("✓ %s (%d → %d bytes)\n", basename($targetPath), (int) filesize($sourcePath), (int) filesize($targetPath)); } else { printf("✗ failed: %s (cwebp exit %d)\n", (string) $sourcePath, $code); $skipped++; } } printf( "Generated %d WebP thumbnail(s); %d failed. %d already present.\n", count($pending) - $skipped, $skipped, $existing ); exit($skipped > 0 ? 1 : 0);