https://github.com/in2code-de/alternative
Automatically set alternative texts for images
https://github.com/in2code-de/alternative
Last synced: 5 months ago
JSON representation
Automatically set alternative texts for images
- Host: GitHub
- URL: https://github.com/in2code-de/alternative
- Owner: in2code-de
- Created: 2025-12-03T21:11:44.000Z (6 months ago)
- Default Branch: main
- Last Pushed: 2026-01-03T16:55:31.000Z (5 months ago)
- Last Synced: 2026-01-05T01:25:04.139Z (5 months ago)
- Language: PHP
- Size: 360 KB
- Stars: 2
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: readme.md
Awesome Lists containing this project
README
# Alternative - AI generated metatags for images in TYPO3 with Google Gemini
## Introduction
This TYPO3 extension allows setting alternative texts, title labels and a description for images in a filestorage.
This can be done via file list backend module or via command on the CLI.
Example metadata labels from AI:

Example backend integration:

Example CLI command:

## Google Gemini API
- To use the extension, you need a **Google Gemini API** key. You can register for one
at https://aistudio.google.com/app/api-keys.
- Alternatively, you can implement your own LLM provider (see [Custom LLM Integration](#custom-llm-integration-like-chatgpt-claude-mistral-etc) below).
## Installation
```
composer req in2code/alternative
```
After that, you have to set some initial configuration in Extension Manager configuration:
| Title | Default value | Description |
|----------------------|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------|
| setAlternative | 1 | Toggle function: Set a value for alternative text |
| setTitle | 1 | Toggle function: Set a value for image title |
| setDescription | 1 | Toggle function: Set a value for a description |
| showButtonInFileList | 1 | Show or hide button in backend module file list |
| apiKey | - | Google Gemini API key. You can let this value empty and simply use ENV_VAR "GOOGLE_API_KEY" instead if you want to use CI pipelines for this setting |
| limitToLanguages | - | If set, limit to this language identifiers only. Use a commaseparated list of numbers |
Note: It's recommended to use ENV vars for in2code/alternative instead of saving the API-Key in Extension Manager configuration
```
GOOGLE_API_KEY=your_api_key_from_google
```
## CLI commands
```
# Set metadata for all image files in storage 1
./vendor/bin/typo3 alternative:set "1:/"
# Set metadata for all image files in a subfoler in storage 1 (maybe "fileadmin/in2code/folder/")
./vendor/bin/typo3 alternative:set "1:/in2code/folder/"
# Enforce to set metadata for all image files in storage 1
./vendor/bin/typo3 alternative:set "1:/" 1
```
## Custom LLM Integration (like ChatGPT, Claude, Mistral, etc.)
Alternative uses a factory pattern to allow custom LLM providers. By default, it uses Google Gemini,
but you can easily integrate other AI services (OpenAI, Claude, local models, etc.).
### Implementing a Custom LLM Repository
1. Create a custom repository class implementing `RepositoryInterface` - see example for OpenAI ChatGPT:
```php
apiKey = getenv('OPENAI_API_KEY') ?: '';
}
public function checkApiKey(): void
{
if ($this->apiKey === '') {
throw new ConfigurationException('OpenAI API key not configured', 1735646000);
}
}
public function getApiUrl(): string
{
return $this->apiUrl;
}
public function analyzeImage(File $file, string $languageCode): array
{
$this->checkApiKey();
$this->setLanguageCode($languageCode);
$imageData = base64_encode($file->getContents());
return $this->generateMetadataWithChatGpt($imageData, $file->getMimeType());
}
protected function generateMetadataWithChatGpt(string $imageData, string $mimeType): array
{
$payload = [
'model' => 'gpt-4o',
'messages' => [
[
'role' => 'user',
'content' => [
['type' => 'text', 'text' => $this->getPrompt()],
[
'type' => 'image_url',
'image_url' => [
'url' => 'data:' . $mimeType . ';base64,' . $imageData,
],
],
],
],
],
'temperature' => 0.1,
'max_tokens' => 500,
];
$options = [
'headers' => [
'Authorization' => 'Bearer ' . $this->apiKey,
'Content-Type' => 'application/json',
],
'body' => json_encode($payload),
];
$response = $this->requestFactory->request($this->getApiUrl(), $this->requestMethod, $options);
if ($response->getStatusCode() !== 200) {
throw new ApiException(
'Failed to analyze image with ChatGPT: ' . $response->getBody()->getContents(),
1735646001
);
}
$responseData = json_decode($response->getBody()->getContents(), true);
return $this->parseResponse($responseData);
}
protected function parseResponse(array $responseData): array
{
if (isset($responseData['choices'][0]['message']['content']) === false) {
throw new ApiException('Invalid ChatGPT API response structure', 1735646002);
}
$text = $responseData['choices'][0]['message']['content'];
// Extract JSON from markdown code blocks if present
if (preg_match('/```json\s*(\{.*?\})\s*```/s', $text, $matches)) {
$text = $matches[1];
} elseif (preg_match('/```\s*(\{.*?\})\s*```/s', $text, $matches)) {
$text = $matches[1];
}
$data = json_decode($text, true);
if ($data === null) {
throw new ApiException('Could not parse ChatGPT response as JSON', 1735646003);
}
return [
'title' => $data['title'] ?? '',
'description' => $data['description'] ?? '',
'alternativeText' => $data['alternativeText'] ?? '',
];
}
}
```
2. Register your custom repository in `ext_localconf.php`:
```php