diff --git a/.gitignore b/.gitignore index 5657f6e..4655933 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -vendor \ No newline at end of file +vendor +pages/*.html +web/previews/*.jpg \ No newline at end of file diff --git a/pages/.gitignore b/pages/.gitignore deleted file mode 100644 index c96a04f..0000000 --- a/pages/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/web/generator.php b/web/generator.php index 40ae3f0..131421b 100644 --- a/web/generator.php +++ b/web/generator.php @@ -4,6 +4,100 @@ declare(strict_types=1); require_once __DIR__ . '/../vendor/autoload.php'; +/** + * Render a JPG preview for a generated page using a local headless browser. + */ +function renderPagePreview(string $seed, string $htmlFilePath): void +{ + $previewDir = __DIR__ . '/previews'; + if (! is_dir($previewDir)) { + mkdir($previewDir, 0775, true); + } + + $realHtmlPath = realpath($htmlFilePath); + if ($realHtmlPath === false) { + return; + } + + $pageUrl = 'file://' . $realHtmlPath; + $previewPngPath = $previewDir . '/' . $seed . '.png'; + $previewJpgPath = $previewDir . '/' . $seed . '.jpg'; + + $browserCandidates = [ + '/usr/bin/google-chrome', + '/usr/bin/google-chrome-stable', + '/usr/bin/chromium', + '/usr/bin/chromium-browser', + 'google-chrome', + 'google-chrome-stable', + 'chromium', + 'chromium-browser', + ]; + $browserBinary = null; + + foreach ($browserCandidates as $candidate) { + $candidatePath = null; + + if (str_starts_with($candidate, '/')) { + if (! is_executable($candidate)) { + continue; + } + $candidatePath = $candidate; + } else { + $pathOut = []; + $pathExitCode = 1; + exec('command -v ' . escapeshellarg($candidate) . ' 2>/dev/null', $pathOut, $pathExitCode); + if ($pathExitCode !== 0 || ! isset($pathOut[0]) || $pathOut[0] === '') { + continue; + } + $candidatePath = $pathOut[0]; + } + + $versionOut = []; + $versionExitCode = 1; + exec(escapeshellarg($candidatePath) . ' --version 2>/dev/null', $versionOut, $versionExitCode); + if ($versionExitCode === 0) { + $browserBinary = $candidatePath; + break; + } + } + + if ($browserBinary === null) { + return; + } + + $command = sprintf( + '%s --headless --disable-gpu --no-sandbox --disable-dev-shm-usage --hide-scrollbars --window-size=1440,900 --screenshot=%s --virtual-time-budget=8000 --run-all-compositor-stages-before-draw %s 2>/dev/null', + escapeshellarg($browserBinary), + escapeshellarg($previewPngPath), + escapeshellarg($pageUrl) + ); + + $output = []; + $exitCode = 1; + exec($command, $output, $exitCode); + + if ($exitCode !== 0 || ! file_exists($previewPngPath)) { + @unlink($previewPngPath); + return; + } + + if (function_exists('imagecreatefrompng') && function_exists('imagejpeg')) { + $image = @imagecreatefrompng($previewPngPath); + if ($image !== false) { + imagejpeg($image, $previewJpgPath, 85); + imagedestroy($image); + } + } + + if (file_exists($previewJpgPath)) { + @unlink($previewPngPath); + } else { + // Keep PNG as fallback if JPG conversion is unavailable. + @rename($previewPngPath, $previewJpgPath); + } +} + header('Content-Type: text/html; charset=UTF-8'); $startedAt = microtime(true); @@ -138,7 +232,9 @@ try { //echo $html; - file_put_contents(__DIR__ . '/../pages/' . $seed . '.html', $html); + $htmlPath = __DIR__ . '/../pages/' . $seed . '.html'; + file_put_contents($htmlPath, $html); + renderPagePreview($seed, $htmlPath); } catch (Throwable $e) { http_response_code(500); diff --git a/web/index.php b/web/index.php index c2715f8..259f588 100644 --- a/web/index.php +++ b/web/index.php @@ -1,25 +1,87 @@ filemtime($b) <=> filemtime($a)); + + echo '
'; + echo ''; + echo 'No generated seed pages found yet.
'; + } else { + echo '' . htmlspecialchars($seed, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '
'; + echo 'Updated: ' . htmlspecialchars($updated, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '
'; + echo '