This commit is contained in:
Laucha1312
2026-06-04 15:00:21 -03:00
parent ed94601e34
commit 47408d49fc
76 changed files with 0 additions and 7737 deletions
-196
View File
@@ -1,196 +0,0 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
class OptimizeImages extends Command
{
protected $signature = 'optimize:images
{--apply : Aplica cambios. Sin esta flag corre en modo dry-run.}
{--folder= : Solo procesa esta carpeta (carousel|sponsors|qr|promos|noticias|clubes).}
{--no-backup : No crea backup de los originales.}';
protected $description = 'Comprime y redimensiona imagenes en storage/app/public/. Por defecto dry-run.';
private array $config = [
'carousel' => ['maxWidth' => 1600, 'quality' => 82],
'noticias' => ['maxWidth' => 1200, 'quality' => 82],
'promos' => ['maxWidth' => 1200, 'quality' => 82],
'clubes' => ['maxWidth' => 512, 'quality' => 85],
'sponsors' => ['maxWidth' => 600, 'quality' => 85],
'qr' => ['maxWidth' => 800, 'quality' => 85],
];
public function handle(): int
{
$apply = (bool) $this->option('apply');
$only = $this->option('folder');
$noBackup = (bool) $this->option('no-backup');
if (!extension_loaded('gd')) {
$this->error('La extension GD de PHP no esta instalada.');
return self::FAILURE;
}
// Usa la config real del disk 'public' (respeta override de Hostinger)
$base = realpath(config('filesystems.disks.public.root')) ?: config('filesystems.disks.public.root');
if (!is_dir($base)) {
$this->error("No existe: $base");
return self::FAILURE;
}
$this->line("Base: $base");
$backupBase = $base . '/_backup_optimize';
if ($apply && !$noBackup && !is_dir($backupBase)) {
mkdir($backupBase, 0755, true);
}
$this->line('');
$this->line($apply
? '<fg=red>MODO APLICACION</> — los archivos seran reemplazados.'
: '<fg=yellow>MODO DRY-RUN</> — no se modifica nada. Pasa --apply para aplicar.');
$this->line('');
$totalOrig = 0; $totalNew = 0; $totalProcessed = 0; $totalSkipped = 0;
foreach ($this->config as $folder => $cfg) {
if ($only && $only !== $folder) continue;
$path = $base . '/' . $folder;
if (!is_dir($path)) {
$this->warn(" - $folder/ (no existe, skip)");
continue;
}
$this->info("Carpeta: $folder/ (max {$cfg['maxWidth']}px, q={$cfg['quality']})");
$files = glob($path . '/*.{jpg,jpeg,png,webp,JPG,JPEG,PNG,WEBP}', GLOB_BRACE);
$folderOrig = 0; $folderNew = 0; $folderProcessed = 0; $folderSkipped = 0;
foreach ($files as $file) {
$origSize = filesize($file);
$totalOrig += $origSize; $folderOrig += $origSize;
$info = @getimagesize($file);
if (!$info) { $folderSkipped++; $totalSkipped++; continue; }
[$w, $h] = $info;
$needsResize = $w > $cfg['maxWidth'];
$needsRecomp = $origSize > 100 * 1024; // skip si ya pesa <100KB
if (!$needsResize && !$needsRecomp) {
$folderSkipped++; $totalSkipped++; $totalNew += $origSize; $folderNew += $origSize;
continue;
}
$result = $this->processImage($file, $cfg['maxWidth'], $cfg['quality']);
if ($result === null) {
$folderSkipped++; $totalSkipped++; $totalNew += $origSize; $folderNew += $origSize;
continue;
}
[$newBytes, $newW, $newH] = $result;
if ($newBytes >= $origSize) {
$folderSkipped++; $totalSkipped++; $totalNew += $origSize; $folderNew += $origSize;
$this->line(sprintf(" - %-40s %dKB no mejora, skip", basename($file), $origSize / 1024));
continue;
}
$folderProcessed++; $totalProcessed++;
$totalNew += $newBytes; $folderNew += $newBytes;
$reduction = round((1 - $newBytes / $origSize) * 100, 1);
$this->line(sprintf(
" %s %-40s %dKB -> %dKB (-%s%%) %dx%d -> %dx%d",
$apply ? '<fg=green>OK</>' : '<fg=cyan>--</>',
basename($file), $origSize / 1024, $newBytes / 1024, $reduction,
$w, $h, $newW, $newH
));
if ($apply) {
if (!$noBackup) {
$backupDir = $backupBase . '/' . $folder;
if (!is_dir($backupDir)) mkdir($backupDir, 0755, true);
copy($file, $backupDir . '/' . basename($file));
}
file_put_contents($file, $result[3]);
}
}
$this->line(sprintf(
" -> %s files procesados, %s skip, %s -> %s (-%s%%)",
$folderProcessed, $folderSkipped,
$this->fmt($folderOrig), $this->fmt($folderNew),
$folderOrig > 0 ? round((1 - $folderNew / max($folderOrig, 1)) * 100, 1) : 0
));
$this->line('');
}
$this->line(str_repeat('=', 60));
$this->info(sprintf(
'TOTAL: %d procesados, %d skip. %s -> %s (ahorro: %s, -%s%%)',
$totalProcessed, $totalSkipped,
$this->fmt($totalOrig), $this->fmt($totalNew),
$this->fmt($totalOrig - $totalNew),
$totalOrig > 0 ? round((1 - $totalNew / max($totalOrig, 1)) * 100, 1) : 0
));
if (!$apply && $totalProcessed > 0) {
$this->line('');
$this->warn('Para aplicar realmente: php artisan optimize:images --apply');
}
return self::SUCCESS;
}
private function processImage(string $file, int $maxWidth, int $quality): ?array
{
$ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
$img = match ($ext) {
'jpg', 'jpeg' => @imagecreatefromjpeg($file),
'png' => @imagecreatefrompng($file),
'webp' => @imagecreatefromwebp($file),
default => null,
};
if (!$img) return null;
$w = imagesx($img); $h = imagesy($img);
$newW = $w; $newH = $h;
if ($w > $maxWidth) {
$newW = $maxWidth;
$newH = (int) round($h * ($maxWidth / $w));
$resized = imagecreatetruecolor($newW, $newH);
if (in_array($ext, ['png', 'webp'])) {
imagealphablending($resized, false);
imagesavealpha($resized, true);
$transparent = imagecolorallocatealpha($resized, 0, 0, 0, 127);
imagefilledrectangle($resized, 0, 0, $newW, $newH, $transparent);
}
imagecopyresampled($resized, $img, 0, 0, 0, 0, $newW, $newH, $w, $h);
$img = $resized;
}
ob_start();
match ($ext) {
'jpg', 'jpeg' => imagejpeg($img, null, $quality),
'png' => imagepng($img, null, 9),
'webp' => imagewebp($img, null, $quality),
};
$bytes = ob_get_clean();
return [strlen($bytes), $newW, $newH, $bytes];
}
private function fmt(int $bytes): string
{
if ($bytes < 1024) return $bytes . 'B';
if ($bytes < 1024 * 1024) return round($bytes / 1024, 1) . 'KB';
return round($bytes / 1024 / 1024, 2) . 'MB';
}
}