=h($m['title'])?>
+=h($m['title'])?>=h($m['title'])?>
=h($m['date'])?> =h($m['location'])?> @@ -412,7 +472,7 @@ $siteHeaderTitle = $headerTitles[array_rand($headerTitles)];
=h($m['title'])?>
+=h($m['title'])?>=h($m['title'])?>
=h($m['date'])?> =h($m['location'])?> diff --git a/new.php b/new.php index d854a17..c6fc559 100644 --- a/new.php +++ b/new.php @@ -62,9 +62,8 @@ function apply_thumbnail_overlay($imagePath, $title, $teaser) { throw new RuntimeException('Thumbnail font file is missing.'); } - $dims = @getimagesize($imagePath); - $imgW = max(1, (int)($dims[0] ?? 1920)); - $imgH = max(1, (int)($dims[1] ?? 1080)); + $imgW = 1200; + $imgH = 630; $normalize = static function ($text): string { $text = trim((string)$text); @@ -92,11 +91,11 @@ function apply_thumbnail_overlay($imagePath, $title, $teaser) { }; $fitTitleText = static function ($text) use ($wrapText): array { $bestText = ''; - $bestSize = 285; - $bestWidth = 18; + $bestSize = 95; + $bestWidth = 28; $bestLines = 1; - for ($size = 320; $size >= 240; $size -= 5) { - $width = max(14, min(28, (int)round(22 + (285 - $size) / 8))); + for ($size = 94; $size >= 60; $size -= 4) { + $width = max(16, min(46, (int)round(24 + (94 - $size) / 2.7))); $candidate = $wrapText($text, $width); $lines = substr_count($candidate, "\n") + 1; $bestText = $candidate; @@ -109,14 +108,14 @@ function apply_thumbnail_overlay($imagePath, $title, $teaser) { }; [$titleText, $titleFontSize, $titleWrapWidth, $titleLines] = $fitTitleText($title); - $teaserText = $clampWrappedText($teaser, 34, 5); + $teaserText = $clampWrappedText($teaser, 38, 6); $teaserLines = max(1, substr_count($teaserText, "\n") + 1); // Move the whole block a bit right while keeping left-aligned text. $left = max(18, (int)round($imgW * 0.055)); $titleY = max(24, (int)round($imgH * 0.06)); - $titleLineStep = max(230, (int)round($imgH * 0.085)); - $teaserY = max((int)round($imgH * 0.27), $titleY + ($titleLines * $titleLineStep) + max(140, (int)round($imgH * 0.06))); + $titleLineStep = max(68, (int)round($imgH * 0.043)); + $teaserY = max((int)round($imgH * 0.25), $titleY + ($titleLines * $titleLineStep) + max(56, (int)round($imgH * 0.03))); $overlayBase = tempnam(sys_get_temp_dir(), 'thumb_overlay_'); $outputPath = dirname($imagePath) . '/' . basename($imagePath) . '.tmp.' . uniqid('', true) . '.jpg'; @@ -127,7 +126,7 @@ function apply_thumbnail_overlay($imagePath, $title, $teaser) { @unlink($overlayBase); $cmd1 = sprintf( - '/usr/bin/convert -size %dx%d xc:none -font %s -pointsize %d -fill "#F3F4F6" -stroke black -strokewidth 7 -interline-spacing 2 -gravity northwest -annotate +%d+%d %s -font %s -pointsize 220 -fill "#EADFC9" -stroke black -strokewidth 6 -interline-spacing -90 -gravity northwest -annotate +%d+%d %s %s 2>&1', + '/usr/bin/convert -size %dx%d xc:none -font %s -pointsize %d -fill "#F3F4F6" -stroke black -strokewidth 2 -interline-spacing 2 -gravity northwest -annotate +%d+%d %s -font %s -pointsize 58 -fill "#EADFC9" -stroke black -strokewidth 2 -interline-spacing -18 -gravity northwest -annotate +%d+%d %s %s 2>&1', $imgW, $imgH, escapeshellarg($titleFontPath), @@ -150,25 +149,54 @@ function apply_thumbnail_overlay($imagePath, $title, $teaser) { throw new RuntimeException('Failed to render thumbnail overlay: ' . trim((string)$out1)); } - $cmd2 = sprintf( - '/usr/bin/convert %s -modulate 56,78 %s -composite %s 2>&1', - escapeshellarg($imagePath), - escapeshellarg($overlayPath), - escapeshellarg($outputPath) - ); - file_put_contents($logFile, "Composite CMD: " . $cmd2 . "\n", FILE_APPEND); - $out2 = shell_exec($cmd2); - file_put_contents($logFile, "Composite Output: " . $out2 . "\n", FILE_APPEND); + $maxBytes = 600 * 1024; + $qualities = [88, 84, 80, 76, 72, 68, 64, 60, 56, 52, 48]; + $finalPath = ''; + $finalSize = 0; + $finalOut = ''; - if (!is_file($outputPath) || filesize($outputPath) < 100) { - @unlink($overlayPath); - @unlink($outputPath); - throw new RuntimeException('Failed to compose thumbnail overlay: ' . trim((string)$out2)); + foreach ($qualities as $quality) { + $attemptPath = dirname($imagePath) . '/' . basename($imagePath) . '.tmp.' . uniqid('', true) . '.jpg'; + $cmd2 = sprintf( + '/usr/bin/convert -filter Lanczos -define filter:blur=0.85 %s -resize %dx%d^ -gravity center -extent %dx%d -modulate 62,76 %s -composite -strip -interlace Plane -sampling-factor 4:2:0 -quality %d %s 2>&1', + escapeshellarg($imagePath), + $imgW, + $imgH, + $imgW, + $imgH, + escapeshellarg($overlayPath), + $quality, + escapeshellarg($attemptPath) + ); + file_put_contents($logFile, "Composite CMD (q=$quality): " . $cmd2 . "\n", FILE_APPEND); + $out2 = shell_exec($cmd2); + file_put_contents($logFile, "Composite Output (q=$quality): " . $out2 . "\n", FILE_APPEND); + + if (!is_file($attemptPath) || filesize($attemptPath) < 100) { + @unlink($attemptPath); + $finalOut = trim((string)$out2); + continue; + } + + $finalPath = $attemptPath; + $finalSize = filesize($attemptPath); + $finalOut = trim((string)$out2); + if ($finalSize <= $maxBytes) break; } - if (!@rename($outputPath, $imagePath)) { + if ($finalPath === '') { @unlink($overlayPath); @unlink($outputPath); + throw new RuntimeException('Failed to compose thumbnail overlay: ' . $finalOut); + } + + if ($finalSize > $maxBytes) { + file_put_contents($logFile, "Thumbnail still above size limit: " . $finalSize . " bytes\n", FILE_APPEND); + } + + if (!@rename($finalPath, $imagePath)) { + @unlink($overlayPath); + @unlink($finalPath); throw new RuntimeException('Failed to replace thumbnail image.'); } @@ -700,13 +728,27 @@ function is_image_media_file($name){ $ext = strtolower(pathinfo((string)$name, PATHINFO_EXTENSION)); return in_array($ext, ['jpg','jpeg','png','webp','gif'], true); } +function public_url_path($path){ + $path = str_replace('\\', '/', trim((string)$path)); + if ($path === '') return ''; + $parts = array_values(array_filter(explode('/', $path), static fn($p) => $p !== '')); + return implode('/', array_map('rawurlencode', $parts)); +} function video_thumb_public_url($config, $videoName){ $videoName = basename((string)$videoName); if ($videoName === '') return ''; - $base = pathinfo($videoName, PATHINFO_FILENAME); - foreach (['jpg','jpeg','png','webp','gif'] as $ext) { - $file = $config['videos_dir'] . '/' . $base . '_thumb.' . $ext; - if (is_file($file)) return $config['public_videos'] . '/' . rawurlencode($base . '_thumb.' . $ext) . '?v=' . filemtime($file); + $info = video_thumb_source_info($config, $videoName); + $sourcePath = (string)($info['source_path'] ?? ''); + if ($sourcePath !== '') { + $abs = str_starts_with($sourcePath, '/') ? $sourcePath : __DIR__ . '/' . ltrim($sourcePath, '/'); + if (is_file($abs)) return public_url_path($sourcePath) . '?v=' . filemtime($abs); + } + $sourceFile = basename((string)($info['source_file'] ?? '')); + $inputDir = basename((string)($info['input_dir'] ?? '')); + if ($sourceFile !== '' && $inputDir !== '') { + $rel = 'in-dir/' . $inputDir . '/' . $sourceFile; + $abs = __DIR__ . '/' . $rel; + if (is_file($abs)) return public_url_path($rel) . '?v=' . filemtime($abs); } return ''; } @@ -1003,6 +1045,19 @@ try { if (!$outputs) throw new RuntimeException('No rendered video found for this input directory yet.'); $src = $dir . '/' . $file; + $thumbName = $articleId . '_og.jpg'; + $target = $config['videos_dir'] . '/' . $thumbName; + if (is_file($target)) @unlink($target); + if (!@copy($src, $target)) { + file_put_contents($logFile, "Failed to copy thumbnail.\n", FILE_APPEND); // LOG 2 + throw new RuntimeException('Failed to write thumbnail.'); + } + @chmod($target, 0664); + + file_put_contents($logFile, "Thumbnail copied, now applying overlay...\n", FILE_APPEND); // LOG 3 + + // Apply text overlay after smart resizing/cropping to 1200x630 + apply_thumbnail_overlay($target, (string)($meta['title'] ?? ''), (string)($meta['teaser'] ?? '')); $written = []; foreach ($outputs as $out) { $base = pathinfo($out, PATHINFO_FILENAME); @@ -1011,25 +1066,13 @@ try { $old = $config['thumbs_dir'] . '/' . $base . '.' . $oldExt; if (is_file($old)) @unlink($old); } - - // Copy new thumbnail to videos_dir with _thumb.jpg suffix - $target = $config['videos_dir'] . '/' . $base . '_thumb.jpg'; - if (!@copy($src, $target)) { - file_put_contents($logFile, "Failed to copy thumbnail.\n", FILE_APPEND); // LOG 2 - throw new RuntimeException('Failed to write thumbnail.'); - } - @chmod($target, 0664); - - file_put_contents($logFile, "Thumbnail copied, now applying overlay...\n", FILE_APPEND); // LOG 3 - - // Apply text overlay - apply_thumbnail_overlay($target, (string)($meta['title'] ?? ''), (string)($meta['teaser'] ?? '')); - $thumbMeta = [ 'article_id' => $articleId, 'input_dir' => basename($dir), 'source_file' => $file, 'source_path' => 'in-dir/' . basename($dir) . '/' . $file, + 'thumb_file' => $thumbName, + 'thumb_path' => 'out-dir/' . $thumbName, 'created_at' => gmdate('c'), ]; $thumbMetaPath = $config['videos_dir'] . '/' . $base . '_thumb.json'; @@ -1037,7 +1080,6 @@ try { throw new RuntimeException('Failed to write thumbnail metadata.'); } @chmod($thumbMetaPath, 0664); - $written[] = basename($target); }