['ext' => 'webm', 'mime' => 'video/webm'], 'mov' => ['ext' => 'mov', 'mime' => 'video/quicktime'], 'webp' => ['ext' => 'webp', 'mime' => 'image/webp'], 'gif' => ['ext' => 'gif', 'mime' => 'image/gif'], ]; /** * Run the database seeds. */ public function run(): void { $this->command->info('šŸš€ Starting meme media import...'); // Ensure media collection exists $this->ensureMediaCollectionExists(); // Read CSV file $csv_path = database_path('seeders/data/webm_metadata.csv'); $meme_data = $this->parseCsvFile($csv_path); $this->command->info('šŸ“Š Found '.count($meme_data).' memes to import'); // Process in chunks for memory efficiency $chunks = array_chunk($meme_data, 50); $total_processed = 0; $total_skipped = 0; $total_failed = 0; foreach ($chunks as $chunk_index => $chunk) { $this->command->info('Processing chunk '.($chunk_index + 1).'/'.count($chunks)); DB::transaction(function () use ($chunk, &$total_processed, &$total_skipped, &$total_failed) { foreach ($chunk as $meme_record) { try { // Extract base filename for duplicate checking $base_filename = pathinfo($meme_record['filename'], PATHINFO_FILENAME); // Check if already exists by checking if webm_url contains this filename if (MemeMedia::where('webm_url', 'like', "%/{$base_filename}.webm")->exists()) { $this->command->warn("ā­ļø Skipping existing: {$meme_record['filename']} ({$meme_record['name']})"); $total_skipped++; continue; } $this->importSingleMeme($meme_record); $total_processed++; if ($total_processed % 10 === 0) { $this->command->info("āœ… Processed {$total_processed} memes..."); } } catch (\Exception $e) { $total_failed++; $this->command->error("āŒ Failed to import: {$meme_record['filename']} - {$e->getMessage()}"); } } }); } // Summary $this->command->info("\nšŸŽÆ Import Summary:"); $this->command->info("āœ… Successfully imported: {$total_processed} memes"); if ($total_skipped > 0) { $this->command->info("ā­ļø Skipped (existing): {$total_skipped} memes"); } if ($total_failed > 0) { $this->command->error("āŒ Failed: {$total_failed} memes"); } $total_media_records = $total_processed * 4; $this->command->info("šŸ“Š Created {$total_media_records} media records and {$total_processed} meme_media records"); } /** * Parse CSV file and return array of meme data */ private function parseCsvFile(string $csv_path): array { if (! file_exists($csv_path)) { throw new \RuntimeException("CSV file not found: {$csv_path}"); } $csv_content = file_get_contents($csv_path); $lines = str_getcsv($csv_content, "\n"); // Parse header row $headers = str_getcsv(array_shift($lines)); $meme_data = []; foreach ($lines as $line) { if (empty(trim($line))) { continue; } $row = str_getcsv($line); if (count($row) === count($headers)) { $meme_data[] = array_combine($headers, $row); } } return $meme_data; } /** * Import a single meme with all its formats */ private function importSingleMeme(array $meme_record): void { // Extract base filename (remove .webm extension) $base_filename = pathinfo($meme_record['filename'], PATHINFO_FILENAME); $media_uuids = []; $media_urls = []; // Create MediaEngine entries for each format foreach (self::FORMATS as $format => $config) { $url = $this->generateCdnUrl($base_filename, $config['ext']); // Create media entry using save_url mode $media = MediaEngine::addMedia( 'temps', // Media collection key $meme_record['type'], // video or image 'system_uploaded', // Media source 'meme_cdn', // Media provider null, // No file content $url, // CDN URL 'save_url', // Mode: just save URL reference null, // Auto-generate filename 'r2', // Disk (not used for URL mode) trim($meme_record['name'])." ({$format})", // Name with format null, // No specific user $config['mime'] // MIME type ); $media_uuids[$format.'_uuid'] = $media->uuid; $media_urls[$format.'_url'] = $url; } $embedding = CloudflareAI::getVectorEmbeddingBgeSmall($meme_record['name'].' '.$meme_record['description'].' '.$meme_record['keywords']); // Create MemeMedia record $meme_media = MemeMedia::where('original_id', $meme_record['filename'])->first(); if (is_null($meme_media)) { MemeMedia::create([ 'original_id' => $meme_record['filename'], 'type' => $meme_record['type'], 'sub_type' => $meme_record['sub_type'], 'name' => trim($meme_record['name']), 'description' => $meme_record['description'], 'keywords' => $meme_record['keywords'], // UUIDs from MediaEngine 'mov_uuid' => $media_uuids['mov_uuid'], 'webm_uuid' => $media_uuids['webm_uuid'], 'gif_uuid' => $media_uuids['gif_uuid'], 'webp_uuid' => $media_uuids['webp_uuid'], // Direct CDN URLs 'mov_url' => $media_urls['mov_url'], 'webm_url' => $media_urls['webm_url'], 'gif_url' => $media_urls['gif_url'], 'webp_url' => $media_urls['webp_url'], // Embedding will be null initially 'embedding' => $embedding, ]); } $this->command->info('āœ… Imported: '.trim($meme_record['name'])); } /** * Generate CDN URL for specific format */ private function generateCdnUrl(string $base_filename, string $extension): string { return self::CDN_BASE_URL."/{$extension}/{$base_filename}.{$extension}"; } /** * Ensure the temps media collection exists */ private function ensureMediaCollectionExists(): void { $collection = MediaCollection::firstOrCreate([ 'key' => 'temps', ], [ 'name' => 'Temp Files', 'description' => 'Temporary and external file references', 'is_system' => true, ]); $this->command->info("šŸ“ Using media collection: {$collection->key}"); } }