282 lines
9.4 KiB
PHP
282 lines
9.4 KiB
PHP
<?php
|
|
|
|
namespace App\Helpers\FirstParty\Meme;
|
|
|
|
use App\Helpers\FirstParty\AI\CloudflareAI;
|
|
use App\Helpers\FirstParty\AI\OpenAI;
|
|
use App\Helpers\FirstParty\AI\RunwareAI;
|
|
use App\Helpers\FirstParty\AspectRatio;
|
|
use App\Helpers\FirstParty\MediaEngine\MediaEngine;
|
|
use App\Models\BackgroundMedia;
|
|
use App\Models\Category;
|
|
use App\Models\Meme;
|
|
use App\Models\MemeMedia;
|
|
use App\Models\MemeMediaEmbedding;
|
|
use Pgvector\Laravel\Distance;
|
|
use Str;
|
|
|
|
class MemeGenerator
|
|
{
|
|
const TYPE_SINGLE_CAPTION_MEME_BACKGROUND = 'single_caption_meme_background';
|
|
|
|
const STATUS_PENDING = 'pending';
|
|
|
|
const STATUS_COMPLETED = 'completed';
|
|
|
|
public static function getSuitableMemeMedia(Meme $meme, $tolerance = 5)
|
|
{
|
|
$meme_media = null;
|
|
|
|
$primary_keyword_type = $meme->primary_keyword_type;
|
|
|
|
if ($primary_keyword_type == 'action') {
|
|
|
|
$meme_media = self::getMemeMediaByKeywords($meme->action_keywords, $tolerance, 'action_keywords');
|
|
|
|
if (is_null($meme_media)) {
|
|
|
|
$keywords = array_merge($meme->emotion_keywords, $meme->misc_keywords, $meme->keywords);
|
|
|
|
$meme_media = self::getMemeMediaByKeywords($keywords, $tolerance);
|
|
}
|
|
} elseif ($primary_keyword_type == 'emotion') {
|
|
$meme_media = self::getMemeMediaByKeywords($meme->emotion_keywords, $tolerance, 'emotion_keywords');
|
|
|
|
if (is_null($meme_media)) {
|
|
$keywords = array_merge($meme->action_keywords, $meme->misc_keywords, $meme->keywords);
|
|
|
|
$meme_media = self::getMemeMediaByKeywords($keywords, $tolerance);
|
|
}
|
|
} elseif ($primary_keyword_type == 'misc') {
|
|
$meme_media = self::getMemeMediaByKeywords($meme->misc_keywords, $tolerance, 'misc_keywords');
|
|
|
|
if (is_null($meme_media)) {
|
|
$keywords = array_merge($meme->action_keywords, $meme->emotion_keywords, $meme->keywords);
|
|
|
|
$meme_media = self::getMemeMediaByKeywords($keywords, $tolerance);
|
|
}
|
|
}
|
|
|
|
if (is_null($meme_media)) {
|
|
$meme_media = MemeMedia::query()->inRandomOrder()->first();
|
|
}
|
|
|
|
return $meme_media;
|
|
}
|
|
|
|
public static function generateMemeByCategory(Category $category)
|
|
{
|
|
$meme_output = self::generateMemeOutputByCategory($category);
|
|
|
|
$meme = null;
|
|
|
|
if ($meme_output->success) {
|
|
$meme = Meme::create([
|
|
'type' => self::TYPE_SINGLE_CAPTION_MEME_BACKGROUND,
|
|
'prompt' => $meme_output->prompt,
|
|
'category_id' => $category->id,
|
|
'caption' => $meme_output->caption,
|
|
'meme_keywords' => $meme_output->keywords,
|
|
'background' => $meme_output->background,
|
|
'keywords' => $meme_output->keywords,
|
|
'is_system' => true,
|
|
'status' => self::STATUS_PENDING,
|
|
'primary_keyword_type' => $meme_output->primary_keyword_type,
|
|
'action_keywords' => $meme_output->action_keywords,
|
|
'emotion_keywords' => $meme_output->emotion_keywords,
|
|
'misc_keywords' => $meme_output->misc_keywords,
|
|
]);
|
|
|
|
$meme->attachTags($meme_output->keywords, 'meme');
|
|
}
|
|
|
|
if (! is_null($meme) && $meme->status == self::STATUS_PENDING) {
|
|
// populate meme_media_id
|
|
$meme->meme_media_id = self::getSuitableMemeMedia($meme)->id;
|
|
$meme->background_media_id = self::generateBackgroundMediaWithRunware($meme_output->background)->id;
|
|
|
|
if (
|
|
// !is_null($meme->meme_media_id) &&
|
|
! is_null($meme->background_media_id)
|
|
) {
|
|
$meme->status = self::STATUS_COMPLETED;
|
|
}
|
|
|
|
$meme->save();
|
|
}
|
|
|
|
return $meme;
|
|
}
|
|
|
|
public static function generateMemeByKeyword($keyword)
|
|
{
|
|
$meme_output = self::generateMemeOutputByKeyword($keyword);
|
|
|
|
$meme = null;
|
|
|
|
if ($meme_output->success) {
|
|
$meme = Meme::create([
|
|
'type' => self::TYPE_SINGLE_CAPTION_MEME_BACKGROUND,
|
|
'prompt' => $meme_output->prompt,
|
|
'category_id' => null,
|
|
'caption' => $meme_output->caption,
|
|
'meme_keywords' => $meme_output->keywords,
|
|
'background' => $meme_output->background,
|
|
'keywords' => $meme_output->keywords,
|
|
'is_system' => true,
|
|
'status' => self::STATUS_PENDING,
|
|
'primary_keyword_type' => $meme_output->primary_keyword_type,
|
|
'action_keywords' => $meme_output->action_keywords,
|
|
'emotion_keywords' => $meme_output->emotion_keywords,
|
|
'misc_keywords' => $meme_output->misc_keywords,
|
|
]);
|
|
|
|
$meme->attachTags($meme_output->keywords, 'meme');
|
|
}
|
|
|
|
if (! is_null($meme) && $meme->status == self::STATUS_PENDING) {
|
|
// populate meme_media_id
|
|
$meme->meme_media_id = self::getSuitableMemeMedia($meme)->id;
|
|
$meme->background_media_id = self::generateBackgroundMediaWithRunware($meme_output->background)->id;
|
|
|
|
if (
|
|
// !is_null($meme->meme_media_id) &&
|
|
! is_null($meme->background_media_id)
|
|
) {
|
|
$meme->status = self::STATUS_COMPLETED;
|
|
}
|
|
|
|
$meme->save();
|
|
}
|
|
|
|
return $meme;
|
|
}
|
|
|
|
public static function generateMemeOutputByKeyword($keyword, $category = null)
|
|
{
|
|
|
|
$retries = 3;
|
|
$attempt = 0;
|
|
|
|
$prompt = "Write me 1 meme about {$keyword}";
|
|
|
|
// RETRY MECHANISM START
|
|
do {
|
|
$attempt++;
|
|
|
|
$meme_response = OpenAI::getSingleMemeGenerator($prompt);
|
|
$meme_output = json_decode(OpenAI::getOpenAIOutput($meme_response));
|
|
|
|
$output_is_valid = false;
|
|
|
|
if (! is_null($meme_output)) {
|
|
if (
|
|
isset($meme_output->caption) &&
|
|
isset($meme_output->background) &&
|
|
isset($meme_output->keywords) &&
|
|
isset($meme_output->primary_keyword_type) &&
|
|
isset($meme_output->action_keywords) &&
|
|
isset($meme_output->emotion_keywords)
|
|
) {
|
|
$output_is_valid = true;
|
|
}
|
|
}
|
|
|
|
// If output is valid or we've exhausted all retries, break the loop
|
|
if ($output_is_valid || $attempt >= $retries) {
|
|
break;
|
|
}
|
|
|
|
// Optional: Add a small delay between retries to avoid rate limiting
|
|
// sleep(1);
|
|
|
|
} while ($attempt < $retries);
|
|
// RETRY MECHANISM END
|
|
|
|
if ($output_is_valid) {
|
|
$meme_output->success = true;
|
|
$meme_output->prompt = $prompt;
|
|
$meme_output->category = $category;
|
|
$meme_output->attempts = $attempt; // Optional: track how many attempts it took
|
|
} else {
|
|
$meme_output = (object) [
|
|
'success' => false,
|
|
'attempts' => $attempt, // Optional: track how many attempts were made
|
|
'error' => 'Failed to generate valid meme after '.$retries.' attempts',
|
|
];
|
|
}
|
|
|
|
return $meme_output;
|
|
}
|
|
|
|
public static function generateMemeOutputByCategory(Category $category)
|
|
{
|
|
|
|
$random_keyword = Str::lower($category->name);
|
|
|
|
if (! is_null($category->parent_id)) {
|
|
$random_keyword = $category->parent->name.' - '.$random_keyword;
|
|
}
|
|
|
|
if (! is_null($category->meme_angles)) {
|
|
$random_keyword .= ' - '.collect($category->meme_angles)->random();
|
|
} elseif (! is_null($category->keywords)) {
|
|
$random_keyword .= ' - '.collect($category->keywords)->random();
|
|
}
|
|
|
|
return self::generateMemeOutputByKeyword($random_keyword, $category);
|
|
}
|
|
|
|
public static function generateBackgroundMediaWithRunware($prompt)
|
|
{
|
|
$media_width = 1024;
|
|
$media_height = 1024;
|
|
$aspect_ratio = AspectRatio::get($media_width, $media_height);
|
|
|
|
$runware_output_url = RunwareAI::generateSchnellImage(Str::uuid(), $prompt, $media_width, $media_height);
|
|
|
|
$media = MediaEngine::addMedia(
|
|
'system-i',
|
|
'image',
|
|
'system_uploaded',
|
|
'replicate',
|
|
null,
|
|
$runware_output_url,
|
|
'download'
|
|
);
|
|
|
|
$background_media = BackgroundMedia::create([
|
|
'media_uuid' => $media->uuid,
|
|
'media_url' => MediaEngine::getMediaCloudUrl($media),
|
|
'prompt' => $prompt,
|
|
'status' => 'completed',
|
|
'aspect_ratio' => $aspect_ratio,
|
|
'media_width' => $media_width,
|
|
'media_height' => $media_height,
|
|
|
|
]);
|
|
|
|
return $background_media;
|
|
}
|
|
|
|
public static function getMemeMediaByKeywords(array $keywords, int $tolerance = 10, ?string $tag = null)
|
|
{
|
|
|
|
$meme_embedding = CloudflareAI::getVectorEmbeddingBgeSmall(implode(' ', $keywords));
|
|
|
|
$meme_medias = MemeMediaEmbedding::query()
|
|
->when(! is_empty($tag), function ($query) use ($tag) {
|
|
return $query->where('tag', $tag);
|
|
})
|
|
->nearestNeighbors('embedding', $meme_embedding, Distance::L2)
|
|
->take($tolerance)
|
|
->get();
|
|
|
|
if ($meme_medias->count() > 0) {
|
|
$meme_media = $meme_medias->random()->meme_media;
|
|
}
|
|
|
|
return $meme_media;
|
|
}
|
|
}
|