Match videos: composed VS-card OG preview + richer share metadata
Sports match share links now show the full VS composition (fighters,
flags, event, weight class, referee/court/match chips) in WhatsApp /
Telegram / Facebook / Twitter previews instead of the raw video frame.
- Browsershot + puppeteer-bundled Chromium screenshots a dedicated
1200x675 (true 16:9) render of the vs-mini composition. Chrome runs
through a small wrapper (bin/browsershot-node.sh) that raises the fd
limit and reads its full env, since PHP-FPM's stripped env otherwise
makes Chromium fail to launch.
- Render is JPEG @ q85 (~100 KB) instead of PNG (~700 KB) — WhatsApp
silently drops OG images above roughly 300 KB, which is why music
thumbnails always showed and match cards didn't.
- SportsMatchController@store/update renders synchronously so the
picture is guaranteed to exist before the user copies the share URL;
edits regenerate and old stamped variants are cleaned. The og:image
URL carries a ?v={updated_at} stamp so scrapers recache on edit.
- accessShare() (/s/{token}) now detects scraper user agents and serves
the video page HTML inline instead of a 302 — some crawlers don't
follow redirects when building link previews.
- og:description / twitter:description built from headerData(): flag
emoji + fighter names (blue vs red), event, weight, match#, court,
referee, view count, upload date.
- Fixed 3-dot menu on video cards being hidden behind the next card
(:has(.dropdown-menu.show) promotes the active card above siblings
and lifts the overflow:hidden clip on .yt-video-info).
- vs-mini: skip the entrance animation on touch devices and expose a
.vs-mini-static class so the OG render always captures the final
frame instead of a random mid-animation moment.
Also adds an admin gallery at /admin/og-previews for spot-checking the
generated preview JPEGs.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
1 parent
665bad76bf
commit
3e18bd2150
14 files changed
+3562
-21
No files matched your search
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Http\Controllers\VideoController;
|
||||
use App\Models\Video;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
/**
|
||||
* Renders the 1200×630 VS composition PNG for a match video's social-share
|
||||
* preview. Runs the same helper used by VideoController@ogImage but from a
|
||||
* CLI process context, because Chromium reliably fails to launch under
|
||||
* PHP-FPM ("Failed to launch the browser process: Code: null") on this
|
||||
* host. Invoke detached via `nohup` from either the ogImage endpoint (on
|
||||
* cache miss) or SportsMatchController on save.
|
||||
*/
|
||||
class RenderMatchOgImage extends Command
|
||||
{
|
||||
protected $signature = 'og:render-match {video}';
|
||||
protected $description = 'Render the OG social-share PNG for a match video';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$video = Video::find($this->argument('video'));
|
||||
if (! $video) {
|
||||
$this->error("Video {$this->argument('video')} not found.");
|
||||
return 1;
|
||||
}
|
||||
if ($video->type !== 'match' || ! $video->sportsMatch) {
|
||||
$this->error("Video {$video->id} is not a match with a sports record.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
$controller = new VideoController();
|
||||
$r = new \ReflectionMethod($controller, 'renderMatchOgImage');
|
||||
$r->setAccessible(true);
|
||||
$png = $r->invoke($controller, $video);
|
||||
|
||||
if ($png === null) {
|
||||
$this->error("Render failed. See laravel.log.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
$this->info("Rendered " . strlen($png) . " bytes for video {$video->id}.");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user