Add (tint)

This commit is contained in:
2023-11-21 01:21:30 +08:00
parent 7166eb610c
commit e4bab8dcec
48 changed files with 229 additions and 58 deletions

View File

@@ -15,11 +15,11 @@ class Kernel extends ConsoleKernel
*/ */
protected function schedule(Schedule $schedule): void protected function schedule(Schedule $schedule): void
{ {
$schedule->command('sitemap:generate')->daily(); $schedule->command('sitemap:generate')->daily()->name('sitemap-generate-daily');
$schedule->call(function () { $schedule->call(function () {
BrowseAndWriteWithAIJob::dispatch()->onQueue('default')->onConnection('default'); BrowseAndWriteWithAIJob::dispatch()->onQueue('default')->onConnection('default');
})->dailyAt('00:00'); })->everySixHours()->name('write-a-job-6hrs');
$schedule->call(function () { $schedule->call(function () {
$future_post = Post::whereNotNull('published_at')->where('status', 'future')->where('published_at', '>=', now())->orderBy('published_at', 'ASC')->first(); $future_post = Post::whereNotNull('published_at')->where('status', 'future')->where('published_at', '>=', now())->orderBy('published_at', 'ASC')->first();
@@ -28,7 +28,7 @@ protected function schedule(Schedule $schedule): void
PublishIndexPostJob::dispatch($future_post->id)->onQueue('default')->onConnection('default'); PublishIndexPostJob::dispatch($future_post->id)->onQueue('default')->onConnection('default');
} }
})->everyMinute(); })->everyMinute()->name('schedule-future-post');
} }

View File

@@ -0,0 +1,171 @@
<?php
namespace App\Helpers\FirstParty\ImageGen;
use Image;
class ImageGen
{
public static function getMainImage($url, $width, $height)
{
$canvas = self::getBaseImage($url, 2.0, $width, $height);
$canvas = self::setImageTint($canvas, $width, $height);
return $canvas;
}
public static function getOpenGraphImage($url, $width, $height, $title = 'Title', $description = 'Description', $website = 'WWW.FUTUREWALKER.CO')
{
$canvas = self::getBaseImage($url, 2.0, $width, $height);
$canvas = self::setImageTint($canvas, $width, $height);
$canvas = self::setOgTint($canvas, $width, $height);
$canvas = self::setOgText($canvas, $title, $description, $website);
return $canvas;
}
private static function setOgText($canvas, $title, $description, $website)
{
$bold_font = resource_path('fonts/RobotoCondensed/RobotoCondensed-Bold.ttf');
$font_color = '#ffffff'; // White color for the text
// Define font sizes
$font_size_title = 65; // Size of the title text
$font_size_description = 28; // Size of the description text
$font_size_website = 28; // Size of the website text
// Define positions
$title_position_y = $canvas->height() - 145; // Position for the title at one-third the height
$description_position_y = $canvas->height() - 30;
$website_position_y = $canvas->height() - 30;
// Title - wrapped to 2 lines if necessary, with ellipsis if there is excess text
$wrappedTitle = wordwrap($title, 35, "\n", true); // Wrap the title
$lines = explode("\n", $wrappedTitle); // Split the title into lines
// Adjust these variables as needed
$lineHeight = 65; // The height of a line of text, adjust as needed for line spacing
$titleStartPositionY = $title_position_y; // Starting Y position for the title, adjust if needed
// Check if there are more than 2 lines after wrapping
if (count($lines) > 2) {
$lines = array_slice($lines, 0, 2); // Keep only the first two lines
$secondLine = &$lines[1]; // Reference to the second line
// Check if the second line can fit an ellipsis; if not, trim the text
if (strlen($secondLine) > 32) {
$secondLine = substr($secondLine, 0, 32) . '...'; // Trim and add ellipsis
} else {
// Add ellipsis directly if there's enough space
$secondLine .= '...';
}
}
// Now, render each line individually with adjusted line spacing
foreach ($lines as $index => $line) {
$currentLineY = $titleStartPositionY + ($index * $lineHeight);
$canvas->text($line, 30, $currentLineY, function($font) use ($bold_font, $font_color, $font_size_title) {
$font->file($bold_font);
$font->size($font_size_title);
$font->color($font_color);
$font->align('left'); // Align text to the left
$font->valign('bottom'); // Align text to the top of the current line position
});
}
// Description - justified to the left, below the title
$canvas->text($description, 30, $description_position_y, function($font) use ($bold_font, $font_color, $font_size_description) {
$font->file($bold_font);
$font->size($font_size_description);
$font->color($font_color);
$font->align('left'); // Align text to the left
$font->valign('bottom'); // Align text to the top (as it is below the title)
});
// Website - justified to the right, at the bottom of the image
$canvas->text($website, $canvas->width() - 30, $website_position_y, function($font) use ($bold_font, $font_color, $font_size_website) {
$font->file($bold_font);
$font->size($font_size_website);
$font->color($font_color);
$font->align('right'); // Align text to the right
$font->valign('bottom'); // Align text to the bottom
});
return $canvas;
}
private static function setOgTint($canvas, $width, $height)
{
$og_tint = Image::make(resource_path("images/tints/caption-bg.png"));
// Stretch the image to the given width and height
$og_tint->resize($width, $height);
$canvas->insert($og_tint, 'top-left', 0, 0);
return $canvas;
}
private static function setImageTint($canvas, $width, $height)
{
/// ADD TINT
// Define the directory path
$directoryPath = resource_path('images/tints');
// Search for files starting with 'tint-' and count them
$tintFilesCount = count(glob($directoryPath . '/tint-*'));
$tint_image = Image::make(resource_path("images/tints/tint-" . rand(1, $tintFilesCount) . ".png"));
// Stretch the image to the given width and height
$tint_image->resize($width, $height);
$canvas->insert($tint_image, 'top-left', 0, 0);
return $canvas;
}
private static function getBaseImage($url, $scale = 1.8, $width = 1007, $height = 567)
{
$original_image = Image::make($url);
// Determine which dimension (width or height) is larger
if ($original_image->width() > $original_image->height()) {
// Width is larger, resize by width
$original_image->resize($width, null, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize(); // Prevent upscaling
});
} else {
// Height is larger or equal, resize by height
$original_image->resize(null, $height, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize(); // Prevent upscaling
});
}
// Scale the image by 1.5x
$scaled_width = $original_image->width() * $scale;
$scaled_height = $original_image->height() * $scale;
$original_image->resize($scaled_width, $scaled_height);
// Create an empty canvas
$canvas = Image::canvas($width, $height);
// Calculate the position to center the scaled image
$x = ($canvas->width() - $scaled_width) / 2;
$y = ($canvas->height() - $scaled_height) / 2;
// Paste the scaled image onto the canvas at the center position
$canvas->insert($original_image, 'top-left', (int) $x, (int) $y);
return $canvas;
}
}

View File

@@ -78,14 +78,14 @@ public function index(Request $request, $category_slug, $slug)
OpenGraph::setUrl(url()->current()); OpenGraph::setUrl(url()->current());
OpenGraph::addProperty('type', 'article'); OpenGraph::addProperty('type', 'article');
OpenGraph::addProperty('locale', 'en_US'); OpenGraph::addProperty('locale', 'en_US');
OpenGraph::addImage($post->featured_image_cdn); OpenGraph::addImage($post->featured_og_image);
$jsonld_multi = JsonLdMulti::newJsonLd(); $jsonld_multi = JsonLdMulti::newJsonLd();
$jsonld_multi->setTitle($post->title) $jsonld_multi->setTitle($post->title)
->setDescription($post_description) ->setDescription($post_description)
->setType('Article') ->setType('Article')
->addValue('headline', $post->title) ->addValue('headline', $post->title)
->addImage($post->featured_image_cdn) ->addImage($post->featured_og_image)
->addValue('author', [ ->addValue('author', [
'type' => 'Person', 'type' => 'Person',
'name' => 'FutureWalker', 'name' => 'FutureWalker',
@@ -96,7 +96,7 @@ public function index(Request $request, $category_slug, $slug)
'name' => 'FutureWalker', 'name' => 'FutureWalker',
'logo' => [ 'logo' => [
'type' => 'ImageObject', 'type' => 'ImageObject',
'url' => asset('FutureWalker-logo-512x512.png'), 'url' => asset('images/icons/icon-512x512.png'),
], ],
]) ])
->addValue('datePublished', $post->published_at->format('Y-m-d')) ->addValue('datePublished', $post->published_at->format('Y-m-d'))

View File

@@ -6,9 +6,21 @@
use Illuminate\Http\Request; use Illuminate\Http\Request;
use LaravelFreelancerNL\LaravelIndexNow\Facades\IndexNow; use LaravelFreelancerNL\LaravelIndexNow\Facades\IndexNow;
use LaravelGoogleIndexing; use LaravelGoogleIndexing;
use App\Helpers\FirstParty\ImageGen\ImageGen;
class TestController extends Controller class TestController extends Controller
{ {
public function imageGen(Request $request)
{
$image_url = 'https://cdn.futurewalker.co/post_images_2/whats-next-for-openai-after-ceo-sam-altmans-ouster-1700439234754.jpg';
$canvas = ImageGen::getOpenGraphImage($image_url, 'Whats Next for OpenAI After CEO Sam Altmans Ouster Whats Next for OpenAI After CEO Sam Altmans Ouster', '20 NOV • OPENAI • SAM ALTMAN • 3 min read ');
return response($canvas->encode('jpeg'))
->header('Content-Type', 'image/jpeg');
}
public function indexing(Request $request) public function indexing(Request $request)
{ {
$url = $request->input('url'); $url = $request->input('url');

View File

@@ -2,6 +2,7 @@
namespace App\Jobs\Tasks; namespace App\Jobs\Tasks;
use App\Helpers\FirstParty\ImageGen\ImageGen;
use App\Helpers\FirstParty\OpenAI\OpenAI; use App\Helpers\FirstParty\OpenAI\OpenAI;
use App\Helpers\FirstParty\OSSUploader\OSSUploader; use App\Helpers\FirstParty\OSSUploader\OSSUploader;
use App\Jobs\SchedulePublishPost; use App\Jobs\SchedulePublishPost;
@@ -184,6 +185,7 @@ private static function setPostImage($post)
$image_content = $image_response->body(); $image_content = $image_response->body();
// Get the size of the image content in KB // Get the size of the image content in KB
$imageSizeInKb = strlen($image_response->body()) / 1024; $imageSizeInKb = strlen($image_response->body()) / 1024;
@@ -192,49 +194,20 @@ private static function setPostImage($post)
continue; continue;
} }
$canvas_width = 1080; $post_description = strtoupper(now()->format('j M')) . "" . $post->main_keyword . "" . markdown_min_read($post->body);
$canvas_height = 608;
$thumb_width = 540; $image = ImageGen::getMainImage($image_content, 1007, 567);
$thumb_height = 78; $og_image = ImageGen::getOpenGraphImage($image_content, 1007, 567, $post->title, $post_description);
// Create an image from the fetched content
$image = Image::make($image_content);
// Check if the image is wider than it is tall (landscape orientation)
if ($image->width() > $image->height()) {
// Resize the image to fill the canvas width while maintaining aspect ratio
$image->resize($canvas_width, null, function ($constraint) {
$constraint->aspectRatio();
});
} else {
// Resize the image to fill the canvas height while maintaining aspect ratio
$image->resize(null, $canvas_height, function ($constraint) {
$constraint->aspectRatio();
});
}
// Fit the image to the canvas size, without gaps
$image->fit($canvas_width, $canvas_height, function ($constraint) {
$constraint->upsize();
});
// Create a thumbnail by cloning the original image and resizing it
$thumb = clone $image;
$thumb->resize($thumb_width, $thumb_height, function ($constraint) {
$constraint->aspectRatio();
$constraint->upsize();
});
// Create a image filename
$epoch_now_timestamp = epoch_now_timestamp(); $epoch_now_timestamp = epoch_now_timestamp();
$filename = $post->slug.'-'.$epoch_now_timestamp.'.jpg'; $filename = $post->slug.'-'.$epoch_now_timestamp.'.jpg';
$thumb_filename = $post->slug.'-'.$epoch_now_timestamp.'_thumb.jpg'; $og_filename = $post->slug.'-'.$epoch_now_timestamp.'_og.jpg';
OSSUploader::uploadFile('r2', 'post_images_2/', $filename, (string) $image->stream('jpeg', 75)); OSSUploader::uploadFile('r2', 'post_images/', $filename, (string) $image->stream('jpeg', 75));
OSSUploader::uploadFile('r2', 'post_images_2/', $thumb_filename, (string) $thumb->stream('jpeg', 50)); OSSUploader::uploadFile('r2', 'post_images/', $og_filename, (string) $og_image->stream('jpeg', 75));
$post->featured_image = 'post_images_2/'.$filename; $post->featured_image = 'post_images/'.$filename;
$post->image_ref_url = $image_ref_url; $post->image_ref_url = $image_ref_url;
$image->destroy(); $image->destroy();

View File

@@ -49,12 +49,24 @@ public static function handle(NewsSerpResult $news_serp_result, $serp_counts = 1
continue; continue;
} }
// check timestamp
$serp_timestamp = Carbon::parse($serp_item->timestamp);
if(!$serp_timestamp->isBetween(now()->subHours(6), now()))
{
continue;
}
/////
$blacklist_keywords = config('platform.global.blacklist_keywords_serp'); $blacklist_keywords = config('platform.global.blacklist_keywords_serp');
$blacklist_domains = config('platform.global.blacklist_domains_serp'); $blacklist_domains = config('platform.global.blacklist_domains_serp');
$skipItem = false; $skipItem = false;
foreach ($blacklist_domains as $domain) { foreach ($blacklist_domains as $domain) {
if (str_contains($serp_item->domain, $domain)) { if (str_contains($serp_item->domain, $domain)) {
$skipItem = true; $skipItem = true;

View File

@@ -81,7 +81,7 @@ protected function featuredImage(): Attribute
); );
} }
protected function getFeaturedThumbImageAttribute() protected function getFeaturedOgImageAttribute()
{ {
$value = $this->featured_image; $value = $this->featured_image;
@@ -91,10 +91,9 @@ protected function getFeaturedThumbImageAttribute()
// Extract the file extension // Extract the file extension
$extension = pathinfo($value, PATHINFO_EXTENSION); $extension = pathinfo($value, PATHINFO_EXTENSION);
// Construct the thumbnail filename by appending '_thumb' before the extension $og = str_replace(".{$extension}", "_og.{$extension}", $value);
$thumbnail = str_replace(".{$extension}", "_thumb.{$extension}", $value);
return $thumbnail; return $og;
// Return the full URL to the thumbnail image // Return the full URL to the thumbnail image
//return Storage::disk('r2')->url($thumbnail); //return Storage::disk('r2')->url($thumbnail);

View File

@@ -29,6 +29,9 @@ public function boot(): void
}); });
$this->routes(function () { $this->routes(function () {
Route::middleware('web')
->prefix('tests')
->group(base_path('routes/tests.php'));
Route::middleware('api') Route::middleware('api')
->prefix('api') ->prefix('api')
->group(base_path('routes/api.php')); ->group(base_path('routes/api.php'));
@@ -36,9 +39,6 @@ public function boot(): void
Route::middleware('web') Route::middleware('web')
->group(base_path('routes/web.php')); ->group(base_path('routes/web.php'));
Route::middleware('web')
->prefix('tests')
->group(base_path('routes/tests.php'));
}); });
} }
} }

BIN
resources/.DS_Store vendored Normal file

Binary file not shown.

BIN
resources/fonts/.DS_Store vendored Normal file

Binary file not shown.

BIN
resources/images/.DS_Store vendored Normal file

Binary file not shown.

BIN
resources/images/tints/.DS_Store vendored Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 758 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 772 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 636 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 599 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 570 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 951 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1022 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 939 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 611 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 958 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 563 KiB

View File

@@ -60,7 +60,7 @@
<a class="text-white mx-3 font-family-roboto-condensed text-uppercase" <a class="text-white mx-3 font-family-roboto-condensed text-uppercase"
href="{{ route('front.home') }}">Home</a> href="{{ route('front.home') }}">Home</a>
@foreach ($categories as $category) @foreach ($categories as $category)
<a href="#" <a href="{{ route('front.category', ['category_slug' => $category->slug]) }}"
class="text-white mx-3 font-family-roboto-condensed text-uppercase">{{ $category->name }}</a> class="text-white mx-3 font-family-roboto-condensed text-uppercase">{{ $category->name }}</a>
@endforeach @endforeach
</div> </div>

View File

@@ -38,7 +38,7 @@
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<div class="position-sticky" style="top: 2rem;"> <div class="position-sticky" style="top: 8rem;">
@include('front.partials.sidebar') @include('front.partials.sidebar')
</div> </div>
</div> </div>

View File

@@ -15,9 +15,9 @@
<div> <div>
@foreach ($post->keywords as $keyword) @foreach ($post->keywords as $keyword)
@if ($keyword == $post->main_keyword) @if ($keyword == $post->main_keyword)
<span class="badge text-bg-dark me-1">{{ $keyword }}</span> <h2 class="badge text-bg-dark me-1 small">{{ $keyword }}</h2>
@else @else
<span class="badge text-bg-light border me-1">{{ $keyword }}</span> <h2 class="badge text-bg-light border me-1 small">{{ $keyword }}</h2>
@endif @endif
@endforeach @endforeach
</div> </div>
@@ -31,7 +31,7 @@
<div class="card mb-3 border-0 shadow-sm"> <div class="card mb-3 border-0 shadow-sm">
<div class="card-body p-4"> <div class="card-body p-4">
<div class="fw-bold mb-2">Quick News Bite:</div> <h3 class="small fw-bold mb-2">Quick News Bite:</h3>
<div><small>{{ $post->bites }}</small></div> <div><small>{{ $post->bites }}</small></div>
</div> </div>
</div> </div>
@@ -48,7 +48,7 @@
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<div class="position-sticky" style="top: 2rem;"> <div class="position-sticky" style="top: 8rem;">
@include('front.partials.sidebar') @include('front.partials.sidebar')
</div> </div>
</div> </div>

View File

@@ -7,10 +7,10 @@
<div class="col-12 col-md-10 col-lg-8"> <div class="col-12 col-md-10 col-lg-8">
<div class="display-6 fw-bold font-family-roboto-condensed mb-2">Your future depends<wbr> on today's <div class="display-6 fw-bold font-family-roboto-condensed mb-2">Your future depends<wbr> on today's
news.</div> news.</div>
<div class="h4 fw-normal mb-4">In the fast-evolving world of AI and tech, staying updated is not <h1 class="h4 fw-normal mb-4">In the fast-evolving world of AI and tech, staying updated is not
optional—it's critical for your future success. Stay updated with daily news from optional—it's critical for your future success. Stay updated with daily news from
<strong>FutureWalker</strong>. <strong>FutureWalker</strong>.
</div> </h1>
<a href="#latest-news" class="btn btn-primary px-4 rounded-pill text-decoration-none">Start reading now</a> <a href="#latest-news" class="btn btn-primary px-4 rounded-pill text-decoration-none">Start reading now</a>
</div> </div>
</div> </div>
@@ -108,7 +108,7 @@ class="btn btn-primary px-4 rounded-pill text-decoration-none">Discover More
</div> </div>
</div> </div>
<div class="col-12 col-lg-4 mb-3"> <div class="col-12 col-lg-4 mb-3">
<div class="position-sticky" style="top: 2rem;"> <div class="position-sticky" style="top: 8rem;">
@include('front.partials.sidebar') @include('front.partials.sidebar')

View File

@@ -33,6 +33,10 @@
| be assigned to the "web" middleware group. Make something great! | be assigned to the "web" middleware group. Make something great!
| |
*/ */
Route::get('/image_gen', [App\Http\Controllers\Tests\TestController::class, 'imageGen']);
Route::get('/incomplete/post', function (Request $request) { Route::get('/incomplete/post', function (Request $request) {
$post = Post::find(1); $post = Post::find(1);