chore: sync AiMediaTools.php to container version (v2.4.0)

This commit is contained in:
2026-06-02 19:07:59 +00:00
parent 15f9061912
commit 91577b8e38
+27 -135
View File
@@ -5,30 +5,17 @@ defined('ABSPATH') || exit;
/** /**
* AI Media Tools — search, generate and import images/videos into WordPress media library. * AI Media Tools — search, generate and import images/videos into WordPress media library.
* *
* Sources supported: * Sources: Pexels (photos + videos), OpenAI GPT Image, Google Gemini / Nano Banana.
* - Pexels (free stock photos + videos, API key stored in wp_options) * All prompts and dimensions are enriched through MediaPromptEngine.
* - GPT Image (OpenAI text-to-image, API key stored in wp_options)
* - Gemini Image (Google Gemini / Nano Banana text-to-image, API key stored in wp_options)
* *
* All tools return: { attachment_id, url, source, width, height, alt } * Admin: WP Admin → AskTam MCP → Settings → AI Media API Keys
* That attachment_id can be dropped straight into any Elementor image/background widget.
*
* Admin: WP Admin → AskTam MCP → Settings → "AI Media API Keys" section.
*/ */
class AiMediaTools { class AiMediaTools {
// ─────────────────────────────────────────────────────────────────────────
// OPTION KEYS
// ─────────────────────────────────────────────────────────────────────────
const OPT_PEXELS_KEY = 'asktam_mcp_pexels_key'; const OPT_PEXELS_KEY = 'asktam_mcp_pexels_key';
const OPT_OPENAI_KEY = 'asktam_mcp_openai_key'; const OPT_OPENAI_KEY = 'asktam_mcp_openai_key';
const OPT_GEMINI_KEY = 'asktam_mcp_gemini_key'; const OPT_GEMINI_KEY = 'asktam_mcp_gemini_key';
// ─────────────────────────────────────────────────────────────────────────
// PEXELS — search photos
// ─────────────────────────────────────────────────────────────────────────
public static function pexelsSearchPhotos(array $args): array { public static function pexelsSearchPhotos(array $args): array {
$key = get_option(self::OPT_PEXELS_KEY, ''); $key = get_option(self::OPT_PEXELS_KEY, '');
if (empty($key)) throw new \RuntimeException('Pexels API key not configured. Go to WP Admin → AskTam MCP → Settings → AI Media API Keys.'); if (empty($key)) throw new \RuntimeException('Pexels API key not configured. Go to WP Admin → AskTam MCP → Settings → AI Media API Keys.');
@@ -68,30 +55,17 @@ class AiMediaTools {
'attachment_id' => null, 'attachment_id' => null,
'wp_url' => null, 'wp_url' => null,
]; ];
if ($import) { if ($import) {
$img_url = $photo['src']['large2x'] ?? $photo['src']['large'] ?? $photo['src']['original']; $img_url = $photo['src']['large2x'] ?? $photo['src']['large'] ?? $photo['src']['original'];
$att = self::importUrlToMedia($img_url, $item['alt'], "Pexels: {$photo['photographer']}"); $att = self::importUrlToMedia($img_url, $item['alt'], "Pexels: {$photo['photographer']}");
if ($att) { if ($att) { $item['attachment_id'] = $att['id']; $item['wp_url'] = $att['url']; }
$item['attachment_id'] = $att['id'];
$item['wp_url'] = $att['url'];
}
} }
$results[] = $item; $results[] = $item;
} }
return [ return ['query' => $query, 'total' => $response['total_results'] ?? count($results), 'results' => $results, 'attribution' => 'Photos provided by Pexels'];
'query' => $query,
'total' => $response['total_results'] ?? count($results),
'results' => $results,
'attribution' => 'Photos provided by Pexels',
];
} }
// ─────────────────────────────────────────────────────────────────────────
// PEXELS — search videos
// ─────────────────────────────────────────────────────────────────────────
public static function pexelsSearchVideos(array $args): array { public static function pexelsSearchVideos(array $args): array {
$key = get_option(self::OPT_PEXELS_KEY, ''); $key = get_option(self::OPT_PEXELS_KEY, '');
if (empty($key)) throw new \RuntimeException('Pexels API key not configured. Go to WP Admin → AskTam MCP → Settings → AI Media API Keys.'); if (empty($key)) throw new \RuntimeException('Pexels API key not configured. Go to WP Admin → AskTam MCP → Settings → AI Media API Keys.');
@@ -119,52 +93,32 @@ class AiMediaTools {
$results = []; $results = [];
foreach ($videos as $video) { foreach ($videos as $video) {
$hd_file = null; $hd_file = null; $sd_file = null;
$sd_file = null;
foreach ($video['video_files'] ?? [] as $file) { foreach ($video['video_files'] ?? [] as $file) {
if ($file['file_type'] !== 'video/mp4') continue; if ($file['file_type'] !== 'video/mp4') continue;
if ($file['quality'] === 'hd' && (!$hd_file || ($file['width'] ?? 0) > ($hd_file['width'] ?? 0))) $hd_file = $file; if ($file['quality'] === 'hd' && (!$hd_file || ($file['width'] ?? 0) > ($hd_file['width'] ?? 0))) $hd_file = $file;
if ($file['quality'] === 'sd' && (!$sd_file || ($file['width'] ?? 0) > ($sd_file['width'] ?? 0))) $sd_file = $file; if ($file['quality'] === 'sd' && (!$sd_file || ($file['width'] ?? 0) > ($sd_file['width'] ?? 0))) $sd_file = $file;
} }
$best = $hd_file ?? $sd_file; $best = $hd_file ?? $sd_file;
$thumb_url = $video['image'] ?? ($video['video_pictures'][0]['picture'] ?? null); $thumb_url = $video['image'] ?? ($video['video_pictures'][0]['picture'] ?? null);
$thumb_att = null; $thumb_att = null;
if ($thumb_url) { if ($thumb_url) $thumb_att = self::importUrlToMedia($thumb_url, $query . ' video thumbnail', 'Pexels Video Thumbnail');
$thumb_att = self::importUrlToMedia($thumb_url, $query . ' video thumbnail', 'Pexels Video Thumbnail');
}
$results[] = [ $results[] = [
'pexels_id' => $video['id'], 'pexels_id' => $video['id'], 'pexels_url' => $video['url'],
'pexels_url' => $video['url'], 'videographer' => $video['user']['name'] ?? '', 'videographer_url' => $video['user']['url'] ?? '',
'videographer' => $video['user']['name'] ?? '', 'duration' => $video['duration'] ?? 0, 'width' => $video['width'] ?? 0, 'height' => $video['height'] ?? 0,
'videographer_url' => $video['user']['url'] ?? '', 'mp4_url' => $best['link'] ?? '', 'mp4_quality' => $best['quality'] ?? '',
'duration' => $video['duration'] ?? 0, 'mp4_width' => $best['width'] ?? 0, 'mp4_height' => $best['height'] ?? 0,
'width' => $video['width'] ?? 0, 'thumbnail_url' => $thumb_url, 'thumbnail_attachment_id' => $thumb_att['id'] ?? null,
'height' => $video['height'] ?? 0,
'mp4_url' => $best['link'] ?? '',
'mp4_quality' => $best['quality'] ?? '',
'mp4_width' => $best['width'] ?? 0,
'mp4_height' => $best['height'] ?? 0,
'thumbnail_url' => $thumb_url,
'thumbnail_attachment_id' => $thumb_att['id'] ?? null,
'thumbnail_wp_url' => $thumb_att['url'] ?? null, 'thumbnail_wp_url' => $thumb_att['url'] ?? null,
]; ];
} }
return [ return ['query' => $query, 'total' => $response['total_results'] ?? count($results), 'results' => $results,
'query' => $query, 'usage_note' => 'Use mp4_url directly in Elementor video widget background. thumbnail_attachment_id as poster image.',
'total' => $response['total_results'] ?? count($results), 'attribution' => 'Videos provided by Pexels'];
'results' => $results,
'usage_note' => 'Use mp4_url directly in Elementor video widget background. thumbnail_attachment_id can be used as poster/fallback image.',
'attribution' => 'Videos provided by Pexels',
];
} }
// ─────────────────────────────────────────────────────────────────────────
// GPT IMAGE — generate
// ─────────────────────────────────────────────────────────────────────────
public static function generateGptImage(array $args): array { public static function generateGptImage(array $args): array {
$key = get_option(self::OPT_OPENAI_KEY, ''); $key = get_option(self::OPT_OPENAI_KEY, '');
if (empty($key)) throw new \RuntimeException('OpenAI API key not configured. Go to WP Admin → AskTam MCP → Settings → AI Media API Keys.'); if (empty($key)) throw new \RuntimeException('OpenAI API key not configured. Go to WP Admin → AskTam MCP → Settings → AI Media API Keys.');
@@ -179,56 +133,22 @@ class AiMediaTools {
if (empty($prompt)) throw new \RuntimeException('prompt is required.'); if (empty($prompt)) throw new \RuntimeException('prompt is required.');
$body = array_filter([ $body = array_filter(['model' => $model, 'prompt' => $prompt, 'n' => $n, 'size' => $size, 'quality' => $quality, 'style' => $style ?: null]);
'model' => $model, $response = self::httpPost('https://api.openai.com/v1/images/generations', $body, ['Authorization' => "Bearer {$key}", 'Content-Type' => 'application/json']);
'prompt' => $prompt,
'n' => $n,
'size' => $size,
'quality' => $quality,
'style' => $style ?: null,
]);
$response = self::httpPost( if (!isset($response['data'])) throw new \RuntimeException("OpenAI API error: " . ($response['error']['message'] ?? json_encode($response)));
'https://api.openai.com/v1/images/generations',
$body,
['Authorization' => "Bearer {$key}", 'Content-Type' => 'application/json']
);
if (!isset($response['data'])) {
$err = $response['error']['message'] ?? json_encode($response);
throw new \RuntimeException("OpenAI API error: {$err}");
}
$results = []; $results = [];
foreach ($response['data'] as $i => $img) { foreach ($response['data'] as $i => $img) {
$fname = count($response['data']) > 1 ? "{$filename}-{$i}" : $filename; $fname = count($response['data']) > 1 ? "{$filename}-{$i}" : $filename;
if (!empty($img['b64_json'])) { $att = !empty($img['b64_json']) ? self::importBase64ToMedia($img['b64_json'], "{$fname}.png", $prompt, 'GPT Image')
$att = self::importBase64ToMedia($img['b64_json'], "{$fname}.png", $prompt, 'GPT Image'); : (!empty($img['url']) ? self::importUrlToMedia($img['url'], $prompt, 'GPT Image') : null);
} elseif (!empty($img['url'])) { if ($att) $results[] = ['attachment_id' => $att['id'], 'url' => $att['url'], 'prompt' => $prompt, 'model' => $model, 'quality' => $quality, 'size' => $size, 'source' => 'openai'];
$att = self::importUrlToMedia($img['url'], $prompt, 'GPT Image');
} else {
continue;
}
if ($att) {
$results[] = [
'attachment_id' => $att['id'],
'url' => $att['url'],
'prompt' => $prompt,
'model' => $model,
'quality' => $quality,
'size' => $size,
'source' => 'openai',
];
}
} }
return ['generated' => count($results), 'results' => $results, 'model' => $model, 'prompt' => $prompt]; return ['generated' => count($results), 'results' => $results, 'model' => $model, 'prompt' => $prompt];
} }
// ─────────────────────────────────────────────────────────────────────────
// GEMINI IMAGE — generate (Google Gemini / Nano Banana)
// ─────────────────────────────────────────────────────────────────────────
public static function generateGeminiImage(array $args): array { public static function generateGeminiImage(array $args): array {
$key = get_option(self::OPT_GEMINI_KEY, ''); $key = get_option(self::OPT_GEMINI_KEY, '');
if (empty($key)) throw new \RuntimeException('Google Gemini API key not configured. Go to WP Admin → AskTam MCP → Settings → AI Media API Keys.'); if (empty($key)) throw new \RuntimeException('Google Gemini API key not configured. Go to WP Admin → AskTam MCP → Settings → AI Media API Keys.');
@@ -242,30 +162,19 @@ class AiMediaTools {
if (empty($prompt)) throw new \RuntimeException('prompt is required.'); if (empty($prompt)) throw new \RuntimeException('prompt is required.');
$endpoint = "https://generativelanguage.googleapis.com/v1beta/models/{$model}:generateContent?key={$key}"; $endpoint = "https://generativelanguage.googleapis.com/v1beta/models/{$model}:generateContent?key={$key}";
$body = ['contents' => [['parts' => [['text' => $prompt]]]], 'generationConfig' => ['responseModalities' => ['TEXT', 'IMAGE'], 'candidateCount' => $n]];
$body = [
'contents' => [['parts' => [['text' => $prompt]]]],
'generationConfig' => ['responseModalities' => ['TEXT', 'IMAGE'], 'candidateCount' => $n],
];
$response = self::httpPost($endpoint, $body, ['Content-Type' => 'application/json']); $response = self::httpPost($endpoint, $body, ['Content-Type' => 'application/json']);
if (isset($response['error'])) { if (isset($response['error'])) throw new \RuntimeException("Gemini API error: " . ($response['error']['message'] ?? json_encode($response['error'])));
throw new \RuntimeException("Gemini API error: " . ($response['error']['message'] ?? json_encode($response['error'])));
}
$results = []; $results = []; $i = 0;
$i = 0;
foreach ($response['candidates'] ?? [] as $candidate) { foreach ($response['candidates'] ?? [] as $candidate) {
foreach ($candidate['content']['parts'] ?? [] as $part) { foreach ($candidate['content']['parts'] ?? [] as $part) {
if (!isset($part['inlineData'])) continue; if (!isset($part['inlineData'])) continue;
$mime = $part['inlineData']['mimeType'] ?? 'image/png'; $mime = $part['inlineData']['mimeType'] ?? 'image/png';
$ext = self::mimeToExt($mime); $fname = ($n > 1) ? "{$filename}-{$i}." . self::mimeToExt($mime) : "{$filename}." . self::mimeToExt($mime);
$fname = ($n > 1) ? "{$filename}-{$i}.{$ext}" : "{$filename}.{$ext}";
$att = self::importBase64ToMedia($part['inlineData']['data'], $fname, $prompt, 'Gemini Image'); $att = self::importBase64ToMedia($part['inlineData']['data'], $fname, $prompt, 'Gemini Image');
if ($att) { if ($att) $results[] = ['attachment_id' => $att['id'], 'url' => $att['url'], 'prompt' => $prompt, 'model' => $model, 'source' => 'gemini'];
$results[] = ['attachment_id' => $att['id'], 'url' => $att['url'], 'prompt' => $prompt, 'model' => $model, 'source' => 'gemini'];
}
$i++; $i++;
} }
} }
@@ -273,10 +182,6 @@ class AiMediaTools {
return ['generated' => count($results), 'results' => $results, 'model' => $model, 'prompt' => $prompt]; return ['generated' => count($results), 'results' => $results, 'model' => $model, 'prompt' => $prompt];
} }
// ─────────────────────────────────────────────────────────────────────────
// STATUS CHECK
// ─────────────────────────────────────────────────────────────────────────
public static function getAiMediaStatus(array $args): array { public static function getAiMediaStatus(array $args): array {
return [ return [
'pexels_configured' => !empty(get_option(self::OPT_PEXELS_KEY, '')), 'pexels_configured' => !empty(get_option(self::OPT_PEXELS_KEY, '')),
@@ -289,26 +194,18 @@ class AiMediaTools {
]; ];
} }
// ─────────────────────────────────────────────────────────────────────────
// PRIVATE HELPERS
// ─────────────────────────────────────────────────────────────────────────
private static function importUrlToMedia(string $url, string $alt = '', string $title = ''): ?array { private static function importUrlToMedia(string $url, string $alt = '', string $title = ''): ?array {
require_once ABSPATH . 'wp-admin/includes/media.php'; require_once ABSPATH . 'wp-admin/includes/media.php';
require_once ABSPATH . 'wp-admin/includes/file.php'; require_once ABSPATH . 'wp-admin/includes/file.php';
require_once ABSPATH . 'wp-admin/includes/image.php'; require_once ABSPATH . 'wp-admin/includes/image.php';
$tmp = download_url($url); $tmp = download_url($url);
if (is_wp_error($tmp)) return null; if (is_wp_error($tmp)) return null;
$basename = basename(parse_url($url, PHP_URL_PATH) ?? 'image.jpg'); $basename = basename(parse_url($url, PHP_URL_PATH) ?? 'image.jpg');
$id = media_handle_sideload(['name' => $basename, 'tmp_name' => $tmp], 0, $title ?: $alt); $id = media_handle_sideload(['name' => $basename, 'tmp_name' => $tmp], 0, $title ?: $alt);
@unlink($tmp); @unlink($tmp);
if (is_wp_error($id)) return null; if (is_wp_error($id)) return null;
if ($alt) update_post_meta($id, '_wp_attachment_image_alt', sanitize_text_field($alt)); if ($alt) update_post_meta($id, '_wp_attachment_image_alt', sanitize_text_field($alt));
if ($title) wp_update_post(['ID' => $id, 'post_title' => sanitize_text_field($title), 'post_excerpt' => sanitize_text_field($alt)]); if ($title) wp_update_post(['ID' => $id, 'post_title' => sanitize_text_field($title), 'post_excerpt' => sanitize_text_field($alt)]);
return ['id' => $id, 'url' => wp_get_attachment_url($id)]; return ['id' => $id, 'url' => wp_get_attachment_url($id)];
} }
@@ -316,20 +213,15 @@ class AiMediaTools {
require_once ABSPATH . 'wp-admin/includes/media.php'; require_once ABSPATH . 'wp-admin/includes/media.php';
require_once ABSPATH . 'wp-admin/includes/file.php'; require_once ABSPATH . 'wp-admin/includes/file.php';
require_once ABSPATH . 'wp-admin/includes/image.php'; require_once ABSPATH . 'wp-admin/includes/image.php';
$data = base64_decode($b64); $data = base64_decode($b64);
if (!$data) return null; if (!$data) return null;
$uploads = wp_upload_dir(); $uploads = wp_upload_dir();
$tmp_path = $uploads['path'] . '/' . sanitize_file_name($filename); $tmp_path = $uploads['path'] . '/' . sanitize_file_name($filename);
if (file_put_contents($tmp_path, $data) === false) return null; if (file_put_contents($tmp_path, $data) === false) return null;
$id = media_handle_sideload(['name' => sanitize_file_name($filename), 'tmp_name' => $tmp_path], 0, $title ?: $alt); $id = media_handle_sideload(['name' => sanitize_file_name($filename), 'tmp_name' => $tmp_path], 0, $title ?: $alt);
if (is_wp_error($id)) { @unlink($tmp_path); return null; } if (is_wp_error($id)) { @unlink($tmp_path); return null; }
if ($alt) update_post_meta($id, '_wp_attachment_image_alt', sanitize_text_field($alt)); if ($alt) update_post_meta($id, '_wp_attachment_image_alt', sanitize_text_field($alt));
if ($title) wp_update_post(['ID' => $id, 'post_title' => sanitize_text_field($title)]); if ($title) wp_update_post(['ID' => $id, 'post_title' => sanitize_text_field($title)]);
return ['id' => $id, 'url' => wp_get_attachment_url($id)]; return ['id' => $id, 'url' => wp_get_attachment_url($id)];
} }