Agrego archivos iniciales
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
<?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';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user