Files
memefast/app/Helpers/FirstParty/Meme/MemeGenerator.php
2025-06-20 20:16:34 +08:00

234 lines
7.6 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)
{
$meme_media = null;
$primary_keyword_type = $meme->primary_keyword_type;
if ($primary_keyword_type == 'action') {
$meme_media = self::getMemeMediaByKeywords($meme->action_keywords, 2, 'action_keywords');
if (is_null($meme_media)) {
$keywords = array_merge($meme->emotion_keywords, $meme->misc_keywords, $meme->keywords);
$meme_media = self::getMemeMediaByKeywords($keywords, 2);
}
} else if ($primary_keyword_type == 'emotion') {
$meme_media = self::getMemeMediaByKeywords($meme->emotion_keywords, 2, 'emotion_keywords');
if (is_null($meme_media)) {
$keywords = array_merge($meme->action_keywords, $meme->misc_keywords, $meme->keywords);
$meme_media = self::getMemeMediaByKeywords($keywords, 2);
}
} else if ($primary_keyword_type == 'misc') {
$meme_media = self::getMemeMediaByKeywords($meme->misc_keywords, 2, 'misc_keywords');
if (is_null($meme_media)) {
$keywords = array_merge($meme->action_keywords, $meme->emotion_keywords, $meme->keywords);
$meme_media = self::getMemeMediaByKeywords($keywords, 2);
}
}
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_id
$meme->meme_id = self::getSuitableMemeMedia($meme)->id;
$meme->background_id = self::generateBackgroundMediaWithRunware($meme_output->background)->id;
if (
//!is_null($meme->meme_id) &&
! is_null($meme->background_id)
) {
$meme->status = self::STATUS_COMPLETED;
}
$meme->save();
}
return $meme;
}
public static function generateMemeOutputByCategory(Category $category)
{
$retries = 3;
$attempt = 0;
$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();
}
$prompt = "Write me 1 meme about {$random_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 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;
}
}