Compare commits

..

No commits in common. "master" and "main" have entirely different histories.
master ... main

26 changed files with 50 additions and 3977 deletions

BIN
.DS_Store vendored

Binary file not shown.

50
.gitignore vendored Normal file
View file

@ -0,0 +1,50 @@
# These are some examples of commonly ignored file patterns.
# You should customize this list as applicable to your project.
# Learn more about .gitignore:
# https://www.atlassian.com/git/tutorials/saving-changes/gitignore
# Node artifact files
node_modules/
dist/
# Compiled Java class files
*.class
# Compiled Python bytecode
*.py[cod]
# Log files
*.log
# Package files
*.jar
# Maven
target/
dist/
# JetBrains IDE
.idea/
# Unit test reports
TEST*.xml
# Generated by MacOS
.DS_Store
# Generated by Windows
Thumbs.db
# Applications
*.app
*.exe
*.war
# Large media files
*.mp4
*.tiff
*.avi
*.flv
*.mov
*.wmv

View file

@ -1,32 +0,0 @@
# Security headers
Header always set X-Content-Type-Options nosniff
Header always set X-Frame-Options DENY
Header always set X-XSS-Protection "1; mode=block"
# Hide sensitive files from web access
<Files .htaccess>
Order Allow,Deny
Deny from all
</Files>
<Files config.php>
Order Allow,Deny
Deny from all
</Files>
# Prevent access to PHP files in generated folder
<FilesMatch "^(generated/).*\.(php|php3|php4|php5|phtml)$">
Order Allow,Deny
Deny from all
</FilesMatch>
# Allow audio files to be served with proper MIME types
AddType audio/mpeg .mp3
AddType audio/wav .wav
AddType audio/ogg .ogg
# Cache audio files
<FilesMatch "\.(mp3|wav|ogg)$">
ExpiresActive On
ExpiresDefault "access plus 1 month"
</FilesMatch>

183
README.md
View file

@ -1,183 +0,0 @@
# 🎵 Sound Effect Generator
A professional web application for generating custom sound effects using the ElevenLabs API with Microsoft Azure AD authentication.
## Features
### 🔐 Authentication
- **Microsoft Azure AD Integration**: Secure login using Oliver SSO
- **Session Management**: Persistent login sessions with automatic token validation
- **Protected Access**: Main application is hidden until user authenticates
### 🎛️ Sound Generation
- **ElevenLabs API Integration**: Professional-grade AI sound generation
- **Text-to-Sound**: Generate sound effects from text descriptions
- **Duration Control**: Adjustable duration from 0.5 to 22 seconds (or auto)
- **Prompt Influence**: Fine-tune how closely the AI follows your description (0-1 scale)
### 🎨 User Interface
- **Modern Design**: Clean, responsive interface using Montserrat font
- **Dark/Light Mode**: Toggle between themes with persistent preference storage
- **Interactive Sliders**: Real-time value display for duration and prompt influence
- **Mobile Responsive**: Optimized for all device sizes
### 🔧 Technical Features
- **Real-time Audio Playback**: Instant preview of generated sounds
- **Download Functionality**: Save sound effects as MP3 files
- **Automatic Cleanup**: Old files (24+ hours) are automatically deleted
- **Error Handling**: Comprehensive error messages and validation
- **Security Headers**: Protected configuration files and proper MIME types
## Installation
### Prerequisites
- Web server with PHP support
- ElevenLabs API key
- Microsoft Azure AD application configured
### Setup Steps
1. **Clone/Download** the project files to your web server
2. **Configure API Key**:
```php
// Edit config.php
'elevenlabs_api_key' => 'your-actual-elevenlabs-api-key-here'
```
3. **Set Permissions**:
```bash
chmod 755 generated/
chmod 644 config.php
```
4. **Configure Azure AD** (if needed):
- Update `clientId` and `authority` in `index.php` line 500-501
- Ensure redirect URI matches your domain
## Configuration
### config.php
```php
return [
'elevenlabs_api_key' => 'your-api-key-here',
'max_file_age_hours' => 24, // Auto-delete files older than this
'generated_files_dir' => 'generated/'
];
```
### Azure AD Settings
Located in `index.php` around line 498:
```javascript
const msalConfig = {
auth: {
clientId: "your-client-id",
authority: "https://login.microsoftonline.com/your-tenant-id",
redirectUri: window.location.origin + window.location.pathname
}
};
```
## Usage
1. **Login**: Use your Oliver SSO credentials to authenticate
2. **Describe Sound**: Enter a text description of the sound effect you want
3. **Adjust Settings**:
- **Duration**: Use slider (0 = auto, or 0.5-22 seconds)
- **Prompt Influence**: Control AI creativity (0 = creative, 1 = literal)
4. **Generate**: Click "Generate Sound Effect" button
5. **Play & Download**: Preview the audio and download if satisfied
### Example Prompts
- "Spacious braam suitable for high-impact movie trailer moments"
- "Gentle rain falling on leaves in a forest"
- "Sci-fi laser gun firing with echo"
- "Medieval sword clashing against shield"
- "Mystical wind chimes in ancient temple"
## API Integration
### ElevenLabs Sound Generation API
- **Endpoint**: `POST https://api.elevenlabs.io/v1/sound-generation`
- **Parameters**:
- `text` (required): Sound description
- `duration_seconds` (optional): 0.5-22 seconds
- `prompt_influence` (optional): 0-1 creativity control
- **Output**: MP3 audio file
## File Structure
```
sound-effects/
├── index.php # Main application
├── config.php # API configuration
├── .htaccess # Security & MIME types
├── generated/ # Generated sound files (auto-created)
└── README.md # This file
```
## Security Features
- **Config Protection**: `.htaccess` prevents direct access to `config.php`
- **File Cleanup**: Automatic deletion of old generated files
- **Input Validation**: Sanitized user inputs and API parameters
- **Session Security**: Secure token management with Azure AD
- **HTTPS Redirect**: Enforced secure connections
## Browser Support
- **Chrome/Edge**: Full support
- **Firefox**: Full support
- **Safari**: Full support (including iOS)
- **Mobile**: Responsive design for all mobile browsers
## Troubleshooting
### Common Issues
**"Please configure your ElevenLabs API key"**
- Edit `config.php` and add your actual API key
**Authentication Issues**
- Check Azure AD configuration
- Verify redirect URI matches your domain
- Clear browser cache and try again
**Sound Generation Fails**
- Verify API key is valid and has credits
- Check internet connection
- Try simpler text descriptions
**File Permission Errors**
- Ensure `generated/` directory exists and is writable
- Check web server file permissions
### Debug Mode
Add error reporting to troubleshoot issues:
```php
// Add to top of index.php for debugging
error_reporting(E_ALL);
ini_set('display_errors', 1);
```
## Development
### Adding Features
- Edit `index.php` for UI/functionality changes
- Modify `config.php` for new settings
- Update `.htaccess` for security rules
### Customization
- **Colors**: Modify CSS variables in the `:root` section
- **Fonts**: Change Google Fonts import and CSS font-family
- **Authentication**: Update Azure AD configuration as needed
## License
This project is for internal use with Oliver Agency's infrastructure and authentication systems.
## Support
For technical issues or questions:
- Email: optical@oliver.agency
- Check ElevenLabs API documentation for sound generation issues
- Verify Azure AD configuration for authentication problems

BIN
V1/.DS_Store vendored

Binary file not shown.

View file

@ -1,10 +0,0 @@
<?php
// Configuration file for ElevenLabs Sound Effect Generator
// IMPORTANT: Replace 'your-api-key-here' with your actual ElevenLabs API key
return [
'elevenlabs_api_key' => 'sk_2505245d89019fb6bbdc1c7a472ea9b537bca4b71a4af411',
'max_file_age_hours' => 24, // Auto-delete generated files older than this
'generated_files_dir' => 'generated/'
];
?>

BIN
V1/generated/.DS_Store vendored

Binary file not shown.

View file

@ -1,452 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ElevenLabs Sound Effect Generator</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
:root {
--primary-btn-color: #f3ae3e;
--primary-btn-hover-color: #d4973b;
--bg-color: #f5f5f5;
--container-bg: white;
--text-color: #333;
--text-muted: #555;
--border-color: #ddd;
--input-bg: white;
--success-bg: #d4edda;
--success-border: #c3e6cb;
--success-text: #155724;
--error-bg: #f8d7da;
--error-border: #f5c6cb;
--error-text: #721c24;
--help-text-color: #666;
}
body {
font-family: 'Montserrat', sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: var(--bg-color);
color: var(--text-color);
transition: background-color 0.3s ease, color 0.3s ease;
}
.container {
background: var(--container-bg);
padding: 30px;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
transition: background-color 0.3s ease;
}
h1 {
color: var(--text-color);
text-align: center;
margin-bottom: 30px;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
color: var(--text-muted);
}
input, textarea, select {
width: 100%;
padding: 12px;
border: 1px solid var(--border-color);
border-radius: 5px;
font-size: 16px;
font-family: 'Montserrat', sans-serif;
font-weight: 300;
background-color: var(--input-bg);
color: var(--text-color);
box-sizing: border-box;
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
}
textarea {
height: 100px;
resize: vertical;
}
button {
background-color: var(--primary-btn-color);
color: white;
padding: 12px 30px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
width: 100%;
transition: background-color 0.3s ease;
}
button:hover {
background-color: var(--primary-btn-hover-color);
}
button:disabled {
background-color: #6c757d;
cursor: not-allowed;
}
.help-text {
font-size: 12px;
color: var(--help-text-color);
margin-top: 5px;
}
.result {
margin-top: 30px;
padding: 20px;
border-radius: 5px;
}
.success {
background-color: var(--success-bg);
border: 1px solid var(--success-border);
color: var(--success-text);
}
.error {
background-color: var(--error-bg);
border: 1px solid var(--error-border);
color: var(--error-text);
}
.loading {
text-align: center;
margin-top: 20px;
}
.audio-player {
margin-top: 20px;
text-align: center;
}
audio {
width: 100%;
max-width: 400px;
}
.slider-container {
position: relative;
margin: 10px 0;
}
.slider {
width: 100%;
height: 8px;
border-radius: 5px;
background: var(--border-color);
outline: none;
opacity: 0.7;
transition: opacity 0.2s, background-color 0.3s ease;
-webkit-appearance: none;
}
.slider:hover {
opacity: 1;
}
.slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 20px;
height: 20px;
border-radius: 50%;
background: var(--primary-btn-color);
cursor: pointer;
}
.slider::-moz-range-thumb {
width: 20px;
height: 20px;
border-radius: 50%;
background: var(--primary-btn-color);
cursor: pointer;
border: none;
}
.slider-value {
display: inline-block;
margin-left: 10px;
font-weight: bold;
color: var(--primary-btn-color);
min-width: 60px;
}
.slider-label {
display: flex;
align-items: center;
justify-content: space-between;
}
/* Dark Mode Toggle Button */
.dark-mode-toggle {
position: fixed;
top: 20px;
right: 20px;
z-index: 1000;
width: 48px;
height: 48px;
min-width: 48px;
min-height: 48px;
border-radius: 24px;
background-color: var(--primary-btn-color);
border: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
color: #000;
transition: background-color 0.3s ease;
flex-shrink: 0;
}
.dark-mode-toggle:hover {
background-color: var(--primary-btn-hover-color);
}
/* Dark Mode Styles */
.dark-mode {
--bg-color: #1e1e1e;
--container-bg: #2a2a2a;
--text-color: #f5f5f5;
--text-muted: #cccccc;
--border-color: #444;
--input-bg: #333;
--success-bg: #1a4a1a;
--success-border: #2d5a2d;
--success-text: #90ee90;
--error-bg: #4a1a1a;
--error-border: #5a2d2d;
--error-text: #ffb3b3;
--help-text-color: #999;
}
.dark-mode .dark-mode-toggle {
color: #fff;
}
</style>
</head>
<body>
<!-- Dark Mode Toggle Button -->
<button id="darkModeToggle" class="dark-mode-toggle" title="Toggle Dark Mode">
<span id="lightModeIcon">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16">
<path d="M8 11a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0 1a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z"/>
</svg>
</span>
<span id="darkModeIcon" style="display: none;">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16">
<path d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278z"/>
</svg>
</span>
</button>
<div class="container">
<h1>🎵 Sound Effect Generator</h1>
<?php
// Load configuration
$config = require_once 'config.php';
// Clean up old files on page load
cleanOldFiles($config['generated_files_dir'], $config['max_file_age_hours']);
$result = '';
$error = '';
$audioFile = '';
function cleanOldFiles($directory, $maxAgeHours) {
if (!is_dir($directory)) {
return;
}
$files = glob($directory . '*.mp3');
$cutoffTime = time() - ($maxAgeHours * 3600);
foreach ($files as $file) {
if (is_file($file) && filemtime($file) < $cutoffTime) {
unlink($file);
}
}
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$text = $_POST['text'] ?? '';
$duration = $_POST['duration_seconds'] ?? null;
$promptInfluence = $_POST['prompt_influence'] ?? 0.3;
$apiKey = $config['elevenlabs_api_key'];
if (empty($text)) {
$error = 'Please enter a description for your sound effect.';
} elseif (empty($apiKey) || $apiKey === 'your-api-key-here') {
$error = 'Please configure your ElevenLabs API key in config.php';
} else {
$audioFile = generateSoundEffect($text, $duration, $promptInfluence, $apiKey, $error);
if ($audioFile) {
$result = 'Sound effect generated successfully!';
}
}
}
function generateSoundEffect($text, $duration, $promptInfluence, $apiKey, &$error) {
$url = 'https://api.elevenlabs.io/v1/sound-generation';
$data = [
'text' => $text,
'prompt_influence' => (float)$promptInfluence
];
if (!empty($duration) && is_numeric($duration)) {
$data['duration_seconds'] = (float)$duration;
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'xi-api-key: ' . $apiKey,
'Content-Type: application/json'
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_TIMEOUT, 60);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if (curl_error($ch)) {
$error = 'Connection error: ' . curl_error($ch);
curl_close($ch);
return false;
}
curl_close($ch);
if ($httpCode === 200) {
$filename = 'sound_effect_' . date('Y-m-d_H-i-s') . '.mp3';
$filepath = __DIR__ . '/generated/' . $filename;
if (!is_dir(__DIR__ . '/generated')) {
mkdir(__DIR__ . '/generated', 0755, true);
}
if (file_put_contents($filepath, $response)) {
return 'generated/' . $filename;
} else {
$error = 'Failed to save the generated sound file.';
return false;
}
} else {
$errorResponse = json_decode($response, true);
$error = 'API Error (' . $httpCode . '): ' . ($errorResponse['detail'] ?? 'Unknown error occurred');
return false;
}
}
?>
<form method="POST" id="soundForm">
<div class="form-group">
<label for="text">Sound Effect Description:</label>
<textarea id="text" name="text" placeholder="e.g., Spacious braam suitable for high-impact movie trailer moments" required><?php echo htmlspecialchars($_POST['text'] ?? ''); ?></textarea>
<div class="help-text">Describe the sound effect you want to generate</div>
</div>
<div class="form-group">
<div class="slider-label">
<label for="duration_seconds">Duration (seconds):</label>
<span class="slider-value" id="duration_value">Auto</span>
</div>
<div class="slider-container">
<input type="range" id="duration_seconds" name="duration_seconds" class="slider" min="0" max="22" step="0.1" value="<?php echo htmlspecialchars($_POST['duration_seconds'] ?? '0'); ?>">
</div>
<div class="help-text">0 = Auto duration, 0.5 to 22 seconds for manual control</div>
</div>
<div class="form-group">
<div class="slider-label">
<label for="prompt_influence">Prompt Influence:</label>
<span class="slider-value" id="influence_value">0.3</span>
</div>
<div class="slider-container">
<input type="range" id="prompt_influence" name="prompt_influence" class="slider" min="0" max="1" step="0.1" value="<?php echo htmlspecialchars($_POST['prompt_influence'] ?? '0.3'); ?>">
</div>
<div class="help-text">0-1: Higher values = more prompt-aligned, lower values = more variable</div>
</div>
<button type="submit" id="generateBtn">Generate Sound Effect</button>
</form>
<?php if ($error): ?>
<div class="result error">
<strong>Error:</strong> <?php echo htmlspecialchars($error); ?>
</div>
<?php endif; ?>
<?php if ($result && $audioFile): ?>
<div class="result success">
<strong><?php echo htmlspecialchars($result); ?></strong>
<div class="audio-player">
<audio controls>
<source src="<?php echo htmlspecialchars($audioFile); ?>" type="audio/mpeg">
Your browser does not support the audio element.
</audio>
<br><br>
<a href="<?php echo htmlspecialchars($audioFile); ?>" download>Download Sound Effect</a>
</div>
</div>
<?php endif; ?>
</div>
<script>
// Dark mode toggle functionality
document.addEventListener('DOMContentLoaded', function() {
// Check for saved dark mode preference
const darkModeEnabled = localStorage.getItem('darkMode') === 'enabled';
if (darkModeEnabled) {
document.body.classList.add('dark-mode');
document.getElementById('lightModeIcon').style.display = 'none';
document.getElementById('darkModeIcon').style.display = 'block';
}
// Toggle dark mode when button is clicked
document.getElementById('darkModeToggle').addEventListener('click', function() {
document.body.classList.toggle('dark-mode');
// Save preference and toggle icons
if (document.body.classList.contains('dark-mode')) {
localStorage.setItem('darkMode', 'enabled');
document.getElementById('lightModeIcon').style.display = 'none';
document.getElementById('darkModeIcon').style.display = 'block';
} else {
localStorage.setItem('darkMode', 'disabled');
document.getElementById('lightModeIcon').style.display = 'block';
document.getElementById('darkModeIcon').style.display = 'none';
}
});
});
// Update slider values in real-time
const durationSlider = document.getElementById('duration_seconds');
const durationValue = document.getElementById('duration_value');
const influenceSlider = document.getElementById('prompt_influence');
const influenceValue = document.getElementById('influence_value');
function updateDurationValue() {
const value = parseFloat(durationSlider.value);
durationValue.textContent = value === 0 ? 'Auto' : value + 's';
}
function updateInfluenceValue() {
influenceValue.textContent = influenceSlider.value;
}
durationSlider.addEventListener('input', updateDurationValue);
influenceSlider.addEventListener('input', updateInfluenceValue);
// Initialize values on page load
updateDurationValue();
updateInfluenceValue();
document.getElementById('soundForm').addEventListener('submit', function() {
const btn = document.getElementById('generateBtn');
btn.disabled = true;
btn.textContent = 'Generating... Please wait';
});
</script>
</body>
</html>

BIN
V2/.DS_Store vendored

Binary file not shown.

View file

@ -1,183 +0,0 @@
# 🎵 Sound Effect Generator
A professional web application for generating custom sound effects using the ElevenLabs API with Microsoft Azure AD authentication.
## Features
### 🔐 Authentication
- **Microsoft Azure AD Integration**: Secure login using Oliver SSO
- **Session Management**: Persistent login sessions with automatic token validation
- **Protected Access**: Main application is hidden until user authenticates
### 🎛️ Sound Generation
- **ElevenLabs API Integration**: Professional-grade AI sound generation
- **Text-to-Sound**: Generate sound effects from text descriptions
- **Duration Control**: Adjustable duration from 0.5 to 22 seconds (or auto)
- **Prompt Influence**: Fine-tune how closely the AI follows your description (0-1 scale)
### 🎨 User Interface
- **Modern Design**: Clean, responsive interface using Montserrat font
- **Dark/Light Mode**: Toggle between themes with persistent preference storage
- **Interactive Sliders**: Real-time value display for duration and prompt influence
- **Mobile Responsive**: Optimized for all device sizes
### 🔧 Technical Features
- **Real-time Audio Playback**: Instant preview of generated sounds
- **Download Functionality**: Save sound effects as MP3 files
- **Automatic Cleanup**: Old files (24+ hours) are automatically deleted
- **Error Handling**: Comprehensive error messages and validation
- **Security Headers**: Protected configuration files and proper MIME types
## Installation
### Prerequisites
- Web server with PHP support
- ElevenLabs API key
- Microsoft Azure AD application configured
### Setup Steps
1. **Clone/Download** the project files to your web server
2. **Configure API Key**:
```php
// Edit config.php
'elevenlabs_api_key' => 'your-actual-elevenlabs-api-key-here'
```
3. **Set Permissions**:
```bash
chmod 755 generated/
chmod 644 config.php
```
4. **Configure Azure AD** (if needed):
- Update `clientId` and `authority` in `index.php` line 500-501
- Ensure redirect URI matches your domain
## Configuration
### config.php
```php
return [
'elevenlabs_api_key' => 'your-api-key-here',
'max_file_age_hours' => 24, // Auto-delete files older than this
'generated_files_dir' => 'generated/'
];
```
### Azure AD Settings
Located in `index.php` around line 498:
```javascript
const msalConfig = {
auth: {
clientId: "your-client-id",
authority: "https://login.microsoftonline.com/your-tenant-id",
redirectUri: window.location.origin + window.location.pathname
}
};
```
## Usage
1. **Login**: Use your Oliver SSO credentials to authenticate
2. **Describe Sound**: Enter a text description of the sound effect you want
3. **Adjust Settings**:
- **Duration**: Use slider (0 = auto, or 0.5-22 seconds)
- **Prompt Influence**: Control AI creativity (0 = creative, 1 = literal)
4. **Generate**: Click "Generate Sound Effect" button
5. **Play & Download**: Preview the audio and download if satisfied
### Example Prompts
- "Spacious braam suitable for high-impact movie trailer moments"
- "Gentle rain falling on leaves in a forest"
- "Sci-fi laser gun firing with echo"
- "Medieval sword clashing against shield"
- "Mystical wind chimes in ancient temple"
## API Integration
### ElevenLabs Sound Generation API
- **Endpoint**: `POST https://api.elevenlabs.io/v1/sound-generation`
- **Parameters**:
- `text` (required): Sound description
- `duration_seconds` (optional): 0.5-22 seconds
- `prompt_influence` (optional): 0-1 creativity control
- **Output**: MP3 audio file
## File Structure
```
sound-effects/
├── index.php # Main application
├── config.php # API configuration
├── .htaccess # Security & MIME types
├── generated/ # Generated sound files (auto-created)
└── README.md # This file
```
## Security Features
- **Config Protection**: `.htaccess` prevents direct access to `config.php`
- **File Cleanup**: Automatic deletion of old generated files
- **Input Validation**: Sanitized user inputs and API parameters
- **Session Security**: Secure token management with Azure AD
- **HTTPS Redirect**: Enforced secure connections
## Browser Support
- **Chrome/Edge**: Full support
- **Firefox**: Full support
- **Safari**: Full support (including iOS)
- **Mobile**: Responsive design for all mobile browsers
## Troubleshooting
### Common Issues
**"Please configure your ElevenLabs API key"**
- Edit `config.php` and add your actual API key
**Authentication Issues**
- Check Azure AD configuration
- Verify redirect URI matches your domain
- Clear browser cache and try again
**Sound Generation Fails**
- Verify API key is valid and has credits
- Check internet connection
- Try simpler text descriptions
**File Permission Errors**
- Ensure `generated/` directory exists and is writable
- Check web server file permissions
### Debug Mode
Add error reporting to troubleshoot issues:
```php
// Add to top of index.php for debugging
error_reporting(E_ALL);
ini_set('display_errors', 1);
```
## Development
### Adding Features
- Edit `index.php` for UI/functionality changes
- Modify `config.php` for new settings
- Update `.htaccess` for security rules
### Customization
- **Colors**: Modify CSS variables in the `:root` section
- **Fonts**: Change Google Fonts import and CSS font-family
- **Authentication**: Update Azure AD configuration as needed
## License
This project is for internal use with Oliver Agency's infrastructure and authentication systems.
## Support
For technical issues or questions:
- Email: optical@oliver.agency
- Check ElevenLabs API documentation for sound generation issues
- Verify Azure AD configuration for authentication problems

View file

@ -1,10 +0,0 @@
<?php
// Configuration file for ElevenLabs Sound Effect Generator
// IMPORTANT: Replace 'your-api-key-here' with your actual ElevenLabs API key
return [
'elevenlabs_api_key' => 'sk_2505245d89019fb6bbdc1c7a472ea9b537bca4b71a4af411',
'max_file_age_hours' => 24, // Auto-delete generated files older than this
'generated_files_dir' => 'generated/'
];
?>

BIN
V2/generated/.DS_Store vendored

Binary file not shown.

View file

@ -1,617 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ElevenLabs Sound Effect Generator</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<!-- Microsoft Authentication Library -->
<script src="https://alcdn.msauth.net/browser/2.15.0/js/msal-browser.min.js" crossorigin="anonymous"></script>
<style>
:root {
--primary-btn-color: #f3ae3e;
--primary-btn-hover-color: #d4973b;
--bg-color: #f5f5f5;
--container-bg: white;
--text-color: #333;
--text-muted: #555;
--border-color: #ddd;
--input-bg: white;
--success-bg: #d4edda;
--success-border: #c3e6cb;
--success-text: #155724;
--error-bg: #f8d7da;
--error-border: #f5c6cb;
--error-text: #721c24;
--help-text-color: #666;
}
body {
font-family: 'Montserrat', sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: var(--bg-color);
color: var(--text-color);
transition: background-color 0.3s ease, color 0.3s ease;
}
.container {
background: var(--container-bg);
padding: 30px;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
transition: background-color 0.3s ease;
}
h1 {
color: var(--text-color);
text-align: center;
margin-bottom: 30px;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
color: var(--text-muted);
}
input, textarea, select {
width: 100%;
padding: 12px;
border: 1px solid var(--border-color);
border-radius: 5px;
font-size: 16px;
font-family: 'Montserrat', sans-serif;
font-weight: 300;
background-color: var(--input-bg);
color: var(--text-color);
box-sizing: border-box;
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
}
textarea {
height: 100px;
resize: vertical;
}
button {
background-color: var(--primary-btn-color);
color: white;
padding: 12px 30px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
width: 100%;
transition: background-color 0.3s ease;
}
button:hover {
background-color: var(--primary-btn-hover-color);
}
button:disabled {
background-color: #6c757d;
cursor: not-allowed;
}
.help-text {
font-size: 12px;
color: var(--help-text-color);
margin-top: 5px;
}
.result {
margin-top: 30px;
padding: 20px;
border-radius: 5px;
}
.success {
background-color: var(--success-bg);
border: 1px solid var(--success-border);
color: var(--success-text);
}
.error {
background-color: var(--error-bg);
border: 1px solid var(--error-border);
color: var(--error-text);
}
.loading {
text-align: center;
margin-top: 20px;
}
.audio-player {
margin-top: 20px;
text-align: center;
}
audio {
width: 100%;
max-width: 400px;
}
.slider-container {
position: relative;
margin: 10px 0;
}
.slider {
width: 100%;
height: 8px;
border-radius: 5px;
background: var(--border-color);
outline: none;
opacity: 0.7;
transition: opacity 0.2s, background-color 0.3s ease;
-webkit-appearance: none;
}
.slider:hover {
opacity: 1;
}
.slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 20px;
height: 20px;
border-radius: 50%;
background: var(--primary-btn-color);
cursor: pointer;
}
.slider::-moz-range-thumb {
width: 20px;
height: 20px;
border-radius: 50%;
background: var(--primary-btn-color);
cursor: pointer;
border: none;
}
.slider-value {
display: inline-block;
margin-left: 10px;
font-weight: bold;
color: var(--primary-btn-color);
min-width: 60px;
}
.slider-label {
display: flex;
align-items: center;
justify-content: space-between;
}
/* Dark Mode Toggle Button */
.dark-mode-toggle {
position: fixed;
top: 20px;
right: 20px;
z-index: 1000;
width: 48px;
height: 48px;
min-width: 48px;
min-height: 48px;
border-radius: 24px;
background-color: var(--primary-btn-color);
border: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
color: #000;
transition: background-color 0.3s ease;
flex-shrink: 0;
}
.dark-mode-toggle:hover {
background-color: var(--primary-btn-hover-color);
}
/* Dark Mode Styles */
.dark-mode {
--bg-color: #1e1e1e;
--container-bg: #2a2a2a;
--text-color: #f5f5f5;
--text-muted: #cccccc;
--border-color: #444;
--input-bg: #333;
--success-bg: #1a4a1a;
--success-border: #2d5a2d;
--success-text: #90ee90;
--error-bg: #4a1a1a;
--error-border: #5a2d2d;
--error-text: #ffb3b3;
--help-text-color: #999;
}
.dark-mode .dark-mode-toggle {
color: #fff;
}
/* Authentication Styles */
#login-container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height: 80vh;
text-align: center;
width: 100%;
max-width: 600px;
margin: 0 auto;
background: var(--container-bg);
border-radius: 10px;
padding: 40px 20px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
#login-container h1 {
font-size: 2rem;
margin-bottom: 20px;
color: var(--text-color);
}
#login-container p {
font-size: 1.1rem;
margin-bottom: 30px;
color: var(--text-muted);
max-width: 80%;
line-height: 1.5;
}
#login-button {
display: flex;
align-items: center;
justify-content: center;
padding: 15px 40px;
font-size: 1.1rem;
background-color: var(--primary-btn-color);
color: white;
border: none;
border-radius: 30px;
cursor: pointer;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
font-weight: 600;
transition: background-color 0.3s ease;
font-family: 'Montserrat', sans-serif;
}
#login-button:hover {
background-color: var(--primary-btn-hover-color);
}
#logout-button {
display: none;
position: absolute;
top: 20px;
right: 80px;
padding: 8px 16px;
font-size: 0.9rem;
background-color: var(--container-bg);
color: var(--text-muted);
border: 1px solid var(--border-color);
border-radius: 5px;
cursor: pointer;
font-family: 'Montserrat', sans-serif;
transition: background-color 0.3s ease;
}
#logout-button:hover {
background-color: var(--border-color);
}
/* Hide protected content by default */
#protected-content {
display: none;
}
.dark-mode #logout-button {
background-color: var(--container-bg);
color: var(--text-muted);
border-color: var(--border-color);
}
</style>
</head>
<body>
<!-- Login Screen -->
<div id="login-container">
<h1>🎵 Sound Effect Generator</h1>
<p>Please log in to access the sound effect generator and create amazing audio for your projects.</p>
<button id="login-button" onclick="signIn()">
Log In with Oliver SSO
</button>
</div>
<!-- Logout Button -->
<button id="logout-button" onclick="signOut()" title="Log Out">
Log Out
</button>
<!-- Dark Mode Toggle Button -->
<button id="darkModeToggle" class="dark-mode-toggle" title="Toggle Dark Mode">
<span id="lightModeIcon">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16">
<path d="M8 11a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0 1a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z"/>
</svg>
</span>
<span id="darkModeIcon" style="display: none;">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16">
<path d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278z"/>
</svg>
</span>
</button>
<!-- Protected Content -->
<div id="protected-content">
<div class="container">
<h1>🎵 Sound Effect Generator</h1>
<?php
// Load configuration
$config = require_once 'config.php';
// Clean up old files on page load
cleanOldFiles($config['generated_files_dir'], $config['max_file_age_hours']);
$result = '';
$error = '';
$audioFile = '';
function cleanOldFiles($directory, $maxAgeHours) {
if (!is_dir($directory)) {
return;
}
$files = glob($directory . '*.mp3');
$cutoffTime = time() - ($maxAgeHours * 3600);
foreach ($files as $file) {
if (is_file($file) && filemtime($file) < $cutoffTime) {
unlink($file);
}
}
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$text = $_POST['text'] ?? '';
$duration = $_POST['duration_seconds'] ?? null;
$promptInfluence = $_POST['prompt_influence'] ?? 0.3;
$apiKey = $config['elevenlabs_api_key'];
if (empty($text)) {
$error = 'Please enter a description for your sound effect.';
} elseif (empty($apiKey) || $apiKey === 'your-api-key-here') {
$error = 'Please configure your ElevenLabs API key in config.php';
} else {
$audioFile = generateSoundEffect($text, $duration, $promptInfluence, $apiKey, $error);
if ($audioFile) {
$result = 'Sound effect generated successfully!';
}
}
}
function generateSoundEffect($text, $duration, $promptInfluence, $apiKey, &$error) {
$url = 'https://api.elevenlabs.io/v1/sound-generation';
$data = [
'text' => $text,
'prompt_influence' => (float)$promptInfluence
];
if (!empty($duration) && is_numeric($duration)) {
$data['duration_seconds'] = (float)$duration;
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'xi-api-key: ' . $apiKey,
'Content-Type: application/json'
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_TIMEOUT, 60);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if (curl_error($ch)) {
$error = 'Connection error: ' . curl_error($ch);
curl_close($ch);
return false;
}
curl_close($ch);
if ($httpCode === 200) {
$filename = 'sound_effect_' . date('Y-m-d_H-i-s') . '.mp3';
$filepath = __DIR__ . '/generated/' . $filename;
if (!is_dir(__DIR__ . '/generated')) {
mkdir(__DIR__ . '/generated', 0755, true);
}
if (file_put_contents($filepath, $response)) {
return 'generated/' . $filename;
} else {
$error = 'Failed to save the generated sound file.';
return false;
}
} else {
$errorResponse = json_decode($response, true);
$error = 'API Error (' . $httpCode . '): ' . ($errorResponse['detail'] ?? 'Unknown error occurred');
return false;
}
}
?>
<form method="POST" id="soundForm">
<div class="form-group">
<label for="text">Sound Effect Description:</label>
<textarea id="text" name="text" placeholder="e.g., Spacious braam suitable for high-impact movie trailer moments" required><?php echo htmlspecialchars($_POST['text'] ?? ''); ?></textarea>
<div class="help-text">Describe the sound effect you want to generate</div>
</div>
<div class="form-group">
<div class="slider-label">
<label for="duration_seconds">Duration (seconds):</label>
<span class="slider-value" id="duration_value">Auto</span>
</div>
<div class="slider-container">
<input type="range" id="duration_seconds" name="duration_seconds" class="slider" min="0" max="22" step="0.1" value="<?php echo htmlspecialchars($_POST['duration_seconds'] ?? '0'); ?>">
</div>
<div class="help-text">0 = Auto duration, 0.5 to 22 seconds for manual control</div>
</div>
<div class="form-group">
<div class="slider-label">
<label for="prompt_influence">Prompt Influence:</label>
<span class="slider-value" id="influence_value">0.3</span>
</div>
<div class="slider-container">
<input type="range" id="prompt_influence" name="prompt_influence" class="slider" min="0" max="1" step="0.1" value="<?php echo htmlspecialchars($_POST['prompt_influence'] ?? '0.3'); ?>">
</div>
<div class="help-text">0-1: Higher values = more prompt-aligned, lower values = more variable</div>
</div>
<button type="submit" id="generateBtn">Generate Sound Effect</button>
</form>
<?php if ($error): ?>
<div class="result error">
<strong>Error:</strong> <?php echo htmlspecialchars($error); ?>
</div>
<?php endif; ?>
<?php if ($result && $audioFile): ?>
<div class="result success">
<strong><?php echo htmlspecialchars($result); ?></strong>
<div class="audio-player">
<audio controls>
<source src="<?php echo htmlspecialchars($audioFile); ?>" type="audio/mpeg">
Your browser does not support the audio element.
</audio>
<br><br>
<a href="<?php echo htmlspecialchars($audioFile); ?>" download>Download Sound Effect</a>
</div>
</div>
<?php endif; ?>
</div>
</div> <!-- End protected content -->
<script>
// Microsoft Authentication Configuration
const msalConfig = {
auth: {
clientId: "9079054c-9620-4757-a256-23413042f1ef",
authority: "https://login.microsoftonline.com/e519c2e6-bc6d-4fdf-8d9c-923c2f002385",
redirectUri: window.location.origin + window.location.pathname
},
cache: {
cacheLocation: "sessionStorage",
storeAuthStateInCookie: true,
}
};
const loginRequest = {
scopes: ["user.read"]
};
const myMSALObj = new msal.PublicClientApplication(msalConfig);
// Authentication Functions
function checkAuthenticationStatus() {
const accessToken = sessionStorage.getItem('accessToken');
if (accessToken) {
showProtectedContent();
} else {
document.getElementById('login-container').style.display = 'flex';
document.getElementById('protected-content').style.display = 'none';
}
}
function signIn() {
myMSALObj.loginPopup(loginRequest)
.then(loginResponse => {
console.log("User logged in:", loginResponse.account.username);
sessionStorage.setItem('accessToken', loginResponse.accessToken);
showProtectedContent();
}).catch(error => {
console.error("Error during login:", error);
document.getElementById('login-container').style.display = 'flex';
document.getElementById('protected-content').style.display = 'none';
});
}
function signOut() {
sessionStorage.removeItem('accessToken');
console.log("User logged out.");
document.getElementById('login-container').style.display = 'flex';
document.getElementById('protected-content').style.display = 'none';
document.getElementById('logout-button').style.display = 'none';
}
function showProtectedContent() {
const accessToken = sessionStorage.getItem('accessToken');
if (accessToken) {
document.getElementById('login-container').style.display = 'none';
document.getElementById('protected-content').style.display = 'block';
document.getElementById('logout-button').style.display = 'block';
}
}
// Initialize authentication on page load
document.addEventListener('DOMContentLoaded', function() {
checkAuthenticationStatus();
// Dark mode toggle functionality
// Check for saved dark mode preference
const darkModeEnabled = localStorage.getItem('darkMode') === 'enabled';
if (darkModeEnabled) {
document.body.classList.add('dark-mode');
document.getElementById('lightModeIcon').style.display = 'none';
document.getElementById('darkModeIcon').style.display = 'block';
}
// Toggle dark mode when button is clicked
document.getElementById('darkModeToggle').addEventListener('click', function() {
document.body.classList.toggle('dark-mode');
// Save preference and toggle icons
if (document.body.classList.contains('dark-mode')) {
localStorage.setItem('darkMode', 'enabled');
document.getElementById('lightModeIcon').style.display = 'none';
document.getElementById('darkModeIcon').style.display = 'block';
} else {
localStorage.setItem('darkMode', 'disabled');
document.getElementById('lightModeIcon').style.display = 'block';
document.getElementById('darkModeIcon').style.display = 'none';
}
});
});
// Update slider values in real-time
const durationSlider = document.getElementById('duration_seconds');
const durationValue = document.getElementById('duration_value');
const influenceSlider = document.getElementById('prompt_influence');
const influenceValue = document.getElementById('influence_value');
function updateDurationValue() {
const value = parseFloat(durationSlider.value);
durationValue.textContent = value === 0 ? 'Auto' : value + 's';
}
function updateInfluenceValue() {
influenceValue.textContent = influenceSlider.value;
}
durationSlider.addEventListener('input', updateDurationValue);
influenceSlider.addEventListener('input', updateInfluenceValue);
// Initialize values on page load
updateDurationValue();
updateInfluenceValue();
document.getElementById('soundForm').addEventListener('submit', function() {
const btn = document.getElementById('generateBtn');
btn.disabled = true;
btn.textContent = 'Generating... Please wait';
});
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.3 KiB

View file

@ -1,10 +0,0 @@
<?php
// Configuration file for ElevenLabs Sound Effect Generator
// IMPORTANT: Replace 'your-api-key-here' with your actual ElevenLabs API key
return [
'elevenlabs_api_key' => 'sk_2505245d89019fb6bbdc1c7a472ea9b537bca4b71a4af411',
'max_file_age_hours' => 24, // Auto-delete generated files older than this
'generated_files_dir' => 'generated/'
];
?>

BIN
generated/.DS_Store vendored

Binary file not shown.

655
index.php
View file

@ -1,655 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ElevenLabs Sound Effect Generator</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<!-- Microsoft Authentication Library -->
<script src="https://alcdn.msauth.net/browser/2.15.0/js/msal-browser.min.js" crossorigin="anonymous"></script>
<style>
:root {
--primary-btn-color: #f3ae3e;
--primary-btn-hover-color: #d4973b;
--bg-color: #f5f5f5;
--container-bg: white;
--text-color: #333;
--text-muted: #555;
--border-color: #ddd;
--input-bg: white;
--success-bg: #d4edda;
--success-border: #c3e6cb;
--success-text: #155724;
--error-bg: #f8d7da;
--error-border: #f5c6cb;
--error-text: #721c24;
--help-text-color: #666;
}
body {
font-family: 'Montserrat', sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: var(--bg-color);
color: var(--text-color);
transition: background-color 0.3s ease, color 0.3s ease;
}
.container {
background: var(--container-bg);
padding: 30px;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
transition: background-color 0.3s ease;
}
h1 {
color: var(--text-color);
text-align: center;
margin-bottom: 30px;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
color: var(--text-muted);
}
input, textarea, select {
width: 100%;
padding: 12px;
border: 1px solid var(--border-color);
border-radius: 5px;
font-size: 16px;
font-family: 'Montserrat', sans-serif;
font-weight: 300;
background-color: var(--input-bg);
color: var(--text-color);
box-sizing: border-box;
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
}
textarea {
height: 100px;
resize: vertical;
}
button {
background-color: var(--primary-btn-color);
color: white;
padding: 12px 30px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
width: 100%;
transition: background-color 0.3s ease;
}
button:hover {
background-color: var(--primary-btn-hover-color);
}
button:disabled {
background-color: #6c757d;
cursor: not-allowed;
}
.help-text {
font-size: 12px;
color: var(--help-text-color);
margin-top: 5px;
}
.result {
margin-top: 30px;
padding: 20px;
border-radius: 5px;
}
.success {
background-color: var(--success-bg);
border: 1px solid var(--success-border);
color: var(--success-text);
}
.error {
background-color: var(--error-bg);
border: 1px solid var(--error-border);
color: var(--error-text);
}
.loading {
text-align: center;
margin-top: 20px;
}
.audio-player {
margin-top: 20px;
text-align: center;
}
audio {
width: 100%;
max-width: 400px;
}
.slider-container {
position: relative;
margin: 10px 0;
}
.slider {
width: 100%;
height: 8px;
border-radius: 5px;
background: var(--border-color);
outline: none;
opacity: 0.7;
transition: opacity 0.2s, background-color 0.3s ease;
-webkit-appearance: none;
}
.slider:hover {
opacity: 1;
}
.slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 20px;
height: 20px;
border-radius: 50%;
background: var(--primary-btn-color);
cursor: pointer;
}
.slider::-moz-range-thumb {
width: 20px;
height: 20px;
border-radius: 50%;
background: var(--primary-btn-color);
cursor: pointer;
border: none;
}
.slider-value {
display: inline-block;
margin-left: 10px;
font-weight: bold;
color: var(--primary-btn-color);
min-width: 60px;
}
.slider-label {
display: flex;
align-items: center;
justify-content: space-between;
}
/* Dark Mode Toggle Button */
.dark-mode-toggle {
position: fixed;
top: 20px;
right: 20px;
z-index: 1000;
width: 48px;
height: 48px;
min-width: 48px;
min-height: 48px;
border-radius: 24px;
background-color: var(--primary-btn-color);
border: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
color: #000;
transition: background-color 0.3s ease;
flex-shrink: 0;
}
.dark-mode-toggle:hover {
background-color: var(--primary-btn-hover-color);
}
/* Dark Mode Styles */
.dark-mode {
--bg-color: #1e1e1e;
--container-bg: #2a2a2a;
--text-color: #f5f5f5;
--text-muted: #cccccc;
--border-color: #444;
--input-bg: #333;
--success-bg: #1a4a1a;
--success-border: #2d5a2d;
--success-text: #90ee90;
--error-bg: #4a1a1a;
--error-border: #5a2d2d;
--error-text: #ffb3b3;
--help-text-color: #999;
}
.dark-mode .dark-mode-toggle {
color: #fff;
}
/* Authentication Styles */
#login-container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height: 80vh;
text-align: center;
width: 100%;
max-width: 600px;
margin: 0 auto;
background: var(--container-bg);
border-radius: 10px;
padding: 40px 20px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
#login-container h1 {
font-size: 2rem;
margin-bottom: 20px;
color: var(--text-color);
}
#login-container p {
font-size: 1.1rem;
margin-bottom: 30px;
color: var(--text-muted);
max-width: 80%;
line-height: 1.5;
}
#login-button {
display: flex;
align-items: center;
justify-content: center;
padding: 15px 40px;
font-size: 1.1rem;
background-color: var(--primary-btn-color);
color: white;
border: none;
border-radius: 30px;
cursor: pointer;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
font-weight: 600;
transition: background-color 0.3s ease;
font-family: 'Montserrat', sans-serif;
}
#login-button:hover {
background-color: var(--primary-btn-hover-color);
}
#logout-button {
display: none;
position: absolute;
top: 20px;
right: 80px;
padding: 8px 16px;
font-size: 0.9rem;
background-color: var(--container-bg);
color: var(--text-muted);
border: 1px solid var(--border-color);
border-radius: 5px;
cursor: pointer;
font-family: 'Montserrat', sans-serif;
transition: background-color 0.3s ease;
}
#logout-button:hover {
background-color: var(--border-color);
}
/* Hide protected content by default */
#protected-content {
display: none;
}
.dark-mode #logout-button {
background-color: var(--container-bg);
color: var(--text-muted);
border-color: var(--border-color);
}
</style>
</head>
<body>
<!-- Login Screen -->
<div id="login-container">
<h1>🎵 Sound Effect Generator</h1>
<p>Please log in to access the sound effect generator and create amazing audio for your projects.</p>
<button id="login-button" onclick="signIn()">
Log In with Oliver SSO
</button>
</div>
<!-- Logout Button -->
<button id="logout-button" onclick="signOut()" title="Log Out">
Log Out
</button>
<!-- Dark Mode Toggle Button -->
<button id="darkModeToggle" class="dark-mode-toggle" title="Toggle Dark Mode">
<span id="lightModeIcon">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16">
<path d="M8 11a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0 1a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z"/>
</svg>
</span>
<span id="darkModeIcon" style="display: none;">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16">
<path d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278z"/>
</svg>
</span>
</button>
<!-- Protected Content -->
<div id="protected-content">
<div class="container">
<h1>🎵 Sound Effect Generator</h1>
<?php
// Load configuration
$config = require_once 'config.php';
// Include webhook processor
require_once 'webhook_processor_audio.php';
// Clean up old files on page load
cleanOldFiles($config['generated_files_dir'], $config['max_file_age_hours']);
$result = '';
$error = '';
$audioFile = '';
function cleanOldFiles($directory, $maxAgeHours) {
if (!is_dir($directory)) {
return;
}
$files = glob($directory . '*.mp3');
$cutoffTime = time() - ($maxAgeHours * 3600);
foreach ($files as $file) {
if (is_file($file) && filemtime($file) < $cutoffTime) {
unlink($file);
}
}
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$text = $_POST['text'] ?? '';
$duration = $_POST['duration_seconds'] ?? null;
$promptInfluence = $_POST['prompt_influence'] ?? 0.3;
$apiKey = $config['elevenlabs_api_key'];
if (empty($text)) {
$error = 'Please enter a description for your sound effect.';
} elseif (empty($apiKey) || $apiKey === 'your-api-key-here') {
$error = 'Please configure your ElevenLabs API key in config.php';
} else {
$audioFile = generateSoundEffect($text, $duration, $promptInfluence, $apiKey, $error);
if ($audioFile) {
$result = 'Sound effect generated successfully!';
}
}
}
function generateSoundEffect($text, $duration, $promptInfluence, $apiKey, &$error) {
$url = 'https://api.elevenlabs.io/v1/sound-generation';
$data = [
'text' => $text,
'prompt_influence' => (float)$promptInfluence
];
if (!empty($duration) && is_numeric($duration)) {
$data['duration_seconds'] = (float)$duration;
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'xi-api-key: ' . $apiKey,
'Content-Type: application/json'
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_TIMEOUT, 60);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if (curl_error($ch)) {
$error = 'Connection error: ' . curl_error($ch);
curl_close($ch);
return false;
}
curl_close($ch);
if ($httpCode === 200) {
$filename = 'sound_effect_' . date('Y-m-d_H-i-s') . '.mp3';
$filepath = __DIR__ . '/generated/' . $filename;
if (!is_dir(__DIR__ . '/generated')) {
mkdir(__DIR__ . '/generated', 0755, true);
}
if (file_put_contents($filepath, $response)) {
// Send webhook for provenance tracking
try {
$webhookProcessor = new AudioWebhookProcessor();
$webhookData = [
'prompt' => $text,
'generation_type' => 'ElevenLabs Sound Effects',
'settings' => [
'prompt' => $text,
'duration_seconds' => $data['duration_seconds'] ?? 'auto',
'prompt_influence' => $data['prompt_influence'],
'model' => 'elevenlabs-sound-generation'
],
'audio_data' => $response, // Raw audio binary data
'client' => 'Oliver Agency',
'user_email' => 'sound-effects@oliver.agency',
'deliverable_number' => '1000000', // Default deliverable number
'additional_data' => [
'filename' => $filename,
'file_size' => strlen($response),
'generation_timestamp' => date('Y-m-d H:i:s'),
'api_endpoint' => 'elevenlabs-sound-generation'
]
];
$webhookSuccess = $webhookProcessor->sendGenerationData($webhookData);
if (!$webhookSuccess) {
error_log("Webhook failed for sound generation: " . $filename);
}
} catch (Exception $e) {
error_log("Webhook error: " . $e->getMessage());
}
return 'generated/' . $filename;
} else {
$error = 'Failed to save the generated sound file.';
return false;
}
} else {
$errorResponse = json_decode($response, true);
$error = 'API Error (' . $httpCode . '): ' . ($errorResponse['detail'] ?? 'Unknown error occurred');
return false;
}
}
?>
<form method="POST" id="soundForm">
<div class="form-group">
<label for="text">Sound Effect Description:</label>
<textarea id="text" name="text" placeholder="e.g., Spacious braam suitable for high-impact movie trailer moments" required><?php echo htmlspecialchars($_POST['text'] ?? ''); ?></textarea>
<div class="help-text">Describe the sound effect you want to generate</div>
</div>
<div class="form-group">
<div class="slider-label">
<label for="duration_seconds">Duration (seconds):</label>
<span class="slider-value" id="duration_value">Auto</span>
</div>
<div class="slider-container">
<input type="range" id="duration_seconds" name="duration_seconds" class="slider" min="0" max="22" step="0.1" value="<?php echo htmlspecialchars($_POST['duration_seconds'] ?? '0'); ?>">
</div>
<div class="help-text">0 = Auto duration, 0.5 to 22 seconds for manual control</div>
</div>
<div class="form-group">
<div class="slider-label">
<label for="prompt_influence">Prompt Influence:</label>
<span class="slider-value" id="influence_value">0.3</span>
</div>
<div class="slider-container">
<input type="range" id="prompt_influence" name="prompt_influence" class="slider" min="0" max="1" step="0.1" value="<?php echo htmlspecialchars($_POST['prompt_influence'] ?? '0.3'); ?>">
</div>
<div class="help-text">0-1: Higher values = more prompt-aligned, lower values = more variable</div>
</div>
<button type="submit" id="generateBtn">Generate Sound Effect</button>
</form>
<?php if ($error): ?>
<div class="result error">
<strong>Error:</strong> <?php echo htmlspecialchars($error); ?>
</div>
<?php endif; ?>
<?php if ($result && $audioFile): ?>
<div class="result success">
<strong><?php echo htmlspecialchars($result); ?></strong>
<div class="audio-player">
<audio controls>
<source src="<?php echo htmlspecialchars($audioFile); ?>" type="audio/mpeg">
Your browser does not support the audio element.
</audio>
<br><br>
<a href="<?php echo htmlspecialchars($audioFile); ?>" download>Download Sound Effect</a>
</div>
</div>
<?php endif; ?>
</div>
</div> <!-- End protected content -->
<script>
// Microsoft Authentication Configuration
const msalConfig = {
auth: {
clientId: "9079054c-9620-4757-a256-23413042f1ef",
authority: "https://login.microsoftonline.com/e519c2e6-bc6d-4fdf-8d9c-923c2f002385",
redirectUri: window.location.origin + window.location.pathname
},
cache: {
cacheLocation: "sessionStorage",
storeAuthStateInCookie: true,
}
};
const loginRequest = {
scopes: ["user.read"]
};
const myMSALObj = new msal.PublicClientApplication(msalConfig);
// Authentication Functions
function checkAuthenticationStatus() {
const accessToken = sessionStorage.getItem('accessToken');
if (accessToken) {
showProtectedContent();
} else {
document.getElementById('login-container').style.display = 'flex';
document.getElementById('protected-content').style.display = 'none';
}
}
function signIn() {
myMSALObj.loginPopup(loginRequest)
.then(loginResponse => {
console.log("User logged in:", loginResponse.account.username);
sessionStorage.setItem('accessToken', loginResponse.accessToken);
showProtectedContent();
}).catch(error => {
console.error("Error during login:", error);
document.getElementById('login-container').style.display = 'flex';
document.getElementById('protected-content').style.display = 'none';
});
}
function signOut() {
sessionStorage.removeItem('accessToken');
console.log("User logged out.");
document.getElementById('login-container').style.display = 'flex';
document.getElementById('protected-content').style.display = 'none';
document.getElementById('logout-button').style.display = 'none';
}
function showProtectedContent() {
const accessToken = sessionStorage.getItem('accessToken');
if (accessToken) {
document.getElementById('login-container').style.display = 'none';
document.getElementById('protected-content').style.display = 'block';
document.getElementById('logout-button').style.display = 'block';
}
}
// Initialize authentication on page load
document.addEventListener('DOMContentLoaded', function() {
checkAuthenticationStatus();
// Dark mode toggle functionality
// Check for saved dark mode preference
const darkModeEnabled = localStorage.getItem('darkMode') === 'enabled';
if (darkModeEnabled) {
document.body.classList.add('dark-mode');
document.getElementById('lightModeIcon').style.display = 'none';
document.getElementById('darkModeIcon').style.display = 'block';
}
// Toggle dark mode when button is clicked
document.getElementById('darkModeToggle').addEventListener('click', function() {
document.body.classList.toggle('dark-mode');
// Save preference and toggle icons
if (document.body.classList.contains('dark-mode')) {
localStorage.setItem('darkMode', 'enabled');
document.getElementById('lightModeIcon').style.display = 'none';
document.getElementById('darkModeIcon').style.display = 'block';
} else {
localStorage.setItem('darkMode', 'disabled');
document.getElementById('lightModeIcon').style.display = 'block';
document.getElementById('darkModeIcon').style.display = 'none';
}
});
});
// Update slider values in real-time
const durationSlider = document.getElementById('duration_seconds');
const durationValue = document.getElementById('duration_value');
const influenceSlider = document.getElementById('prompt_influence');
const influenceValue = document.getElementById('influence_value');
function updateDurationValue() {
const value = parseFloat(durationSlider.value);
durationValue.textContent = value === 0 ? 'Auto' : value + 's';
}
function updateInfluenceValue() {
influenceValue.textContent = influenceSlider.value;
}
durationSlider.addEventListener('input', updateDurationValue);
influenceSlider.addEventListener('input', updateInfluenceValue);
// Initialize values on page load
updateDurationValue();
updateInfluenceValue();
document.getElementById('soundForm').addEventListener('submit', function() {
const btn = document.getElementById('generateBtn');
btn.disabled = true;
btn.textContent = 'Generating... Please wait';
});
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.3 KiB

View file

@ -1,116 +0,0 @@
<?php
/**
* Test Webhook Integration for Sound Effects Generator
*
* This script sends a sample webhook with mock data to test the integration
* Run this file directly to test webhook connectivity and data format
*/
// Include the audio webhook processor
require_once 'webhook_processor_audio.php';
// Create a small mock MP3 file (minimal valid MP3 header + silent audio)
function createMockMp3Data() {
// This is a minimal valid MP3 file with about 1 second of silence
// MP3 frame header + minimal audio data
$mp3Header = "\xFF\xFB\x90\x00"; // MP3 frame header (MPEG-1 Layer 3, 128kbps, 44.1kHz, mono)
$mockAudioData = str_repeat("\x00", 1024); // Silent audio data
return $mp3Header . $mockAudioData;
}
echo "🎵 Testing Sound Effects Webhook Integration\n";
echo "==========================================\n\n";
try {
// Initialize webhook processor
$webhookProcessor = new AudioWebhookProcessor([
'client' => 'Oliver Agency - Test',
'user_email' => 'webhook-test@oliver.agency'
]);
echo "✅ Webhook processor initialized\n";
// Test webhook connectivity first
echo "🔗 Testing webhook connectivity...\n";
$connectivityTest = $webhookProcessor->testWebhook();
if ($connectivityTest['success']) {
echo "✅ Webhook endpoint is reachable\n";
echo " Response time: " . $connectivityTest['response_time_ms'] . "ms\n";
echo " HTTP code: " . $connectivityTest['http_code'] . "\n\n";
} else {
echo "❌ Webhook endpoint test failed\n";
echo " HTTP code: " . $connectivityTest['http_code'] . "\n";
echo " Error: " . $connectivityTest['error'] . "\n\n";
}
// Create mock sound generation data
$mockAudioData = createMockMp3Data();
$timestamp = date('Y-m-d_H-i-s');
$testWebhookData = [
'prompt' => 'Test sound: Epic cinematic boom with reverb (WEBHOOK TEST)',
'generation_type' => 'ElevenLabs Sound Effects',
'settings' => [
'prompt' => 'Test sound: Epic cinematic boom with reverb (WEBHOOK TEST)',
'duration_seconds' => 3.5,
'prompt_influence' => 0.7,
'model' => 'elevenlabs-sound-generation',
'test_mode' => true
],
'audio_data' => $mockAudioData,
'client' => 'Oliver Agency - Webhook Test',
'user_email' => 'webhook-test@oliver.agency',
'deliverable_number' => '1000000',
'additional_data' => [
'filename' => 'test_sound_effect_' . $timestamp . '.mp3',
'file_size' => strlen($mockAudioData),
'generation_timestamp' => date('Y-m-d H:i:s'),
'api_endpoint' => 'elevenlabs-sound-generation',
'test_source' => 'webhook_test_script',
'test_description' => 'Mock sound generation for webhook testing'
]
];
echo "📦 Prepared test webhook data:\n";
echo " Prompt: " . $testWebhookData['prompt'] . "\n";
echo " Duration: " . $testWebhookData['settings']['duration_seconds'] . " seconds\n";
echo " Prompt Influence: " . $testWebhookData['settings']['prompt_influence'] . "\n";
echo " Audio Data Size: " . strlen($mockAudioData) . " bytes\n";
echo " Deliverable Number: " . $testWebhookData['deliverable_number'] . "\n\n";
// Send the webhook
echo "🚀 Sending test webhook...\n";
$webhookSuccess = $webhookProcessor->sendGenerationData($testWebhookData);
if ($webhookSuccess) {
echo "✅ Test webhook sent successfully!\n";
echo " Data should now be visible in your webhook automation system\n";
echo " Look for deliverable: " . $testWebhookData['deliverable_number'] . "\n";
} else {
echo "❌ Test webhook failed to send\n";
echo " Check error logs for more details\n";
}
echo "\n📊 Webhook Configuration:\n";
$config = $webhookProcessor->getConfig();
foreach ($config as $key => $value) {
if (is_array($value)) {
echo " $key: " . json_encode($value) . "\n";
} else {
echo " $key: $value\n";
}
}
} catch (Exception $e) {
echo "❌ Test failed with exception: " . $e->getMessage() . "\n";
echo " Stack trace: " . $e->getTraceAsString() . "\n";
}
echo "\n🔍 Next Steps:\n";
echo "1. Check your webhook automation system for the test data\n";
echo "2. Look for deliverable number starting with 'TEST-'\n";
echo "3. Verify all fields are received correctly\n";
echo "4. Check that the audio file (base64 encoded MP3) is properly formatted\n";
echo "\nTest completed at: " . date('Y-m-d H:i:s') . "\n";
?>

View file

@ -1,198 +0,0 @@
# Webhook Deployment Guide
## Overview
This system provides a standardized webhook integration for AI image generation applications. It captures user prompts, generation settings, and resulting images, then sends them to external automation systems via webhook.
## Key Components
### Core Files
- `normalize_image.php` - Image processing and webhook utilities
- `webhook_processor.php` - Reusable webhook deployment module (see below)
- Various `process_*.php` files - Provider-specific implementations
### Main Functions
- `sendStandardizedWebhook()` - Sends data to webhook endpoint
- `buildWebhookData()` - Creates standardized data structure
- `normalizeImageForWebhook()` - Processes images to standard format
## What Gets Sent to Webhooks
### Standard Data Structure
```json
{
"client": "Client Name",
"deliverableNumber": "12345",
"userEmail": "user@domain.com",
"generationType": "DALL-E 3",
"settings": {
"prompt": "User's text prompt",
"model": "dall-e-3",
"quality": "standard"
},
"imageFile": "data:image/jpeg;base64,/9j/4AAQSkZJRgABA...",
"timestamp": 1642723200
}
```
### Supported AI Providers
- **DALL-E 3** - OpenAI image generation
- **Leonardo AI** - Custom models and characters
- **Adobe Firefly** - Commercial-safe generation
- **Bria AI** - Business-focused generation
- **Ideogram** - Text-in-image generation
- **Stable Diffusion 3** - Open source generation
- **Flux** - Advanced diffusion models
## Webhook Endpoint
Default endpoint: `https://hook.us1.make.celonis.com/sbhcpk0athbdbxxmgijxc5sbwtjsg33h`
## Quick Deployment
### 1. Copy Required Files
```bash
# Copy core webhook functionality
cp normalize_image.php /path/to/your/app/
cp webhook_processor.php /path/to/your/app/
# Copy any specific process files you need
cp process_dalle3.php /path/to/your/app/
cp process_leonardo.php /path/to/your/app/
# etc.
```
### 2. Update Configuration
```php
// In your config file, set the webhook URL
define('WEBHOOK_URL', 'https://your-webhook-endpoint.com');
// Set default values
define('DEFAULT_CLIENT', 'Your-Company');
define('DEFAULT_EMAIL', 'admin@yourcompany.com');
```
### 3. Basic Integration
```php
// Include the webhook processor
require_once 'webhook_processor.php';
// Initialize with your settings
$processor = new WebhookProcessor([
'webhook_url' => 'https://your-webhook.com',
'client' => 'Your Client Name',
'user_email' => 'user@company.com'
]);
// Send image generation data
$processor->sendGenerationData([
'prompt' => 'Generate a sunset landscape',
'generation_type' => 'DALL-E 3',
'settings' => ['quality' => 'hd'],
'image_data' => $imageBytes,
'deliverable_number' => '12345'
]);
```
## Advanced Configuration
### Custom Data Processing
```php
// Add custom data before sending
$webhookData = buildWebhookData([
'client' => $client,
'deliverableNumber' => $deliverableNumber,
'userEmail' => $userEmail,
'generationType' => 'Custom AI',
'settings' => $customSettings,
'imageData' => $imageBytes,
'additionalData' => [
'custom_field' => 'custom_value',
'processing_time' => $executionTime
]
]);
sendStandardizedWebhook($webhookData, $customWebhookUrl);
```
### Error Handling
```php
$success = sendStandardizedWebhook($webhookData);
if (!$success) {
// Handle webhook failure
error_log("Webhook failed for deliverable: " . $deliverableNumber);
// Optionally store for retry
}
```
## Security Considerations
1. **Webhook URL Protection** - Use HTTPS endpoints only
2. **Data Sanitization** - All user inputs are JSON encoded
3. **Image Processing** - Images are normalized and base64 encoded
4. **SSL Verification** - cURL uses SSL peer verification
## Monitoring & Debugging
### Log Webhook Calls
```php
// Add logging before sending
$logData = [
'webhook_url' => $webhookUrl,
'data_size' => strlen(json_encode($webhookData)),
'client' => $webhookData['client'],
'timestamp' => date('Y-m-d H:i:s')
];
error_log("Webhook send: " . json_encode($logData));
```
### Test Webhook Endpoint
```bash
curl -X POST https://your-webhook.com \
-H "Content-Type: application/json" \
-d '{"test": "data", "timestamp": 1642723200}'
```
## Integration Examples
### WordPress Plugin
```php
add_action('wp_ajax_generate_ai_image', function() {
require_once plugin_dir_path(__FILE__) . 'webhook_processor.php';
$processor = new WebhookProcessor([
'webhook_url' => get_option('ai_webhook_url'),
'client' => get_option('ai_client_name')
]);
// Process generation and send webhook
});
```
### Laravel Controller
```php
use App\Services\WebhookProcessor;
class AIGenerationController extends Controller {
public function generate(Request $request) {
$processor = new WebhookProcessor([
'webhook_url' => config('services.webhook.url'),
'client' => auth()->user()->company
]);
$processor->sendGenerationData($request->all());
}
}
```
## Troubleshooting
### Common Issues
1. **SSL Certificate Errors** - Ensure webhook endpoint has valid SSL
2. **Large Image Timeouts** - Increase cURL timeout for large images
3. **Memory Issues** - Process images in chunks for very large files
### Testing Checklist
- [ ] Webhook endpoint responds to POST requests
- [ ] JSON data is properly formatted
- [ ] Images are base64 encoded correctly
- [ ] Required fields are present (client, deliverableNumber, etc.)
- [ ] SSL certificates are valid

View file

@ -1,344 +0,0 @@
<?php
/**
* Reusable Webhook Processor for AI Image Generation Applications
*
* This class provides a standardized way to integrate webhook functionality
* into any application that generates AI images. Simply include this file
* and use the WebhookProcessor class to send generation data to external
* automation systems.
*
* Usage:
* require_once 'webhook_processor.php';
* $processor = new WebhookProcessor(['webhook_url' => 'https://your-hook.com']);
* $processor->sendGenerationData($data);
*/
class WebhookProcessor {
private $config;
private $defaultWebhookUrl = 'https://hook.us1.make.celonis.com/sbhcpk0athbdbxxmgijxc5sbwtjsg33h';
/**
* Initialize webhook processor with configuration
*
* @param array $config Configuration options:
* - webhook_url: Target webhook URL (optional)
* - client: Default client name (optional)
* - user_email: Default user email (optional)
* - deliverable_number: Default deliverable number (optional)
* - timeout: cURL timeout in seconds (default: 30)
* - verify_ssl: Whether to verify SSL certificates (default: true)
*/
public function __construct($config = []) {
$this->config = array_merge([
'webhook_url' => $this->defaultWebhookUrl,
'client' => 'Auto-Approved',
'user_email' => 'ai-comp-setup@oliver.agency',
'deliverable_number' => '00000',
'timeout' => 30,
'verify_ssl' => true
], $config);
}
/**
* Send AI generation data to webhook
*
* @param array $data Generation data:
* - prompt: The user's text prompt (required)
* - generation_type: Type of AI generation (DALL-E 3, Leonardo, etc.)
* - settings: Generation settings array
* - image_data: Raw image binary data OR image URL OR file path
* - image_url: Alternative to image_data - URL to download image
* - image_file: Alternative to image_data - file path to read image
* - client: Client name (overrides default)
* - user_email: User email (overrides default)
* - deliverable_number: Deliverable number (overrides default)
* - additional_data: Extra data to include in webhook
* @param string $webhookUrl Optional webhook URL override
* @return bool True on success, false on failure
*/
public function sendGenerationData($data, $webhookUrl = null) {
$webhookUrl = $webhookUrl ?: $this->config['webhook_url'];
// Build standardized webhook data
$webhookData = $this->buildWebhookData($data);
// Send the webhook
return $this->sendWebhook($webhookData, $webhookUrl);
}
/**
* Build standardized webhook data structure
*
* @param array $data Input data
* @return array Standardized webhook data
*/
private function buildWebhookData($data) {
$webhookData = [
'client' => $data['client'] ?? $this->config['client'],
'deliverableNumber' => $data['deliverable_number'] ?? $this->config['deliverable_number'],
'userEmail' => $data['user_email'] ?? $this->config['user_email'],
'generationType' => $data['generation_type'] ?? 'Unknown',
'settings' => $data['settings'] ?? [],
'timestamp' => time(),
];
// Add prompt to settings if provided
if (isset($data['prompt'])) {
$webhookData['settings']['prompt'] = $data['prompt'];
}
// Process image data if provided
if (isset($data['image_data'])) {
$normalized = $this->normalizeImageData($data['image_data']);
if ($normalized) {
$webhookData['imageFile'] = $normalized['base64'];
}
} elseif (isset($data['image_url'])) {
$normalized = $this->processImageUrl($data['image_url']);
if ($normalized) {
$webhookData['imageFile'] = $normalized['base64'];
}
} elseif (isset($data['image_file'])) {
$normalized = $this->processImageFile($data['image_file']);
if ($normalized) {
$webhookData['imageFile'] = $normalized['base64'];
}
}
// Add any additional data
if (isset($data['additional_data']) && is_array($data['additional_data'])) {
$webhookData = array_merge($webhookData, $data['additional_data']);
}
return $webhookData;
}
/**
* Normalize image data to standard format
*
* @param mixed $imageData Raw binary data, base64 string, or data URI
* @param string $targetFormat Target format (jpeg, png)
* @param int $quality JPEG quality (1-100)
* @return array|null Normalized image data or null on failure
*/
private function normalizeImageData($imageData, $targetFormat = 'jpeg', $quality = 90) {
// If already a data URI, extract the data
if (is_string($imageData) && strpos($imageData, 'data:') === 0) {
$parts = explode(',', $imageData, 2);
if (count($parts) === 2) {
$imageData = base64_decode($parts[1]);
}
}
// If base64 encoded string, decode it
elseif (is_string($imageData) && base64_decode($imageData, true) !== false) {
$imageData = base64_decode($imageData);
}
// Create image resource from binary data
$image = @imagecreatefromstring($imageData);
if ($image === false) {
// Try to detect mime type and return original
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mimeType = $finfo->buffer($imageData);
return [
'mime_type' => $mimeType,
'base64' => 'data:' . $mimeType . ';base64,' . base64_encode($imageData),
'data' => $imageData
];
}
// Convert to target format
ob_start();
if ($targetFormat === 'jpeg' || $targetFormat === 'jpg') {
imagejpeg($image, null, $quality);
$mimeType = 'image/jpeg';
} elseif ($targetFormat === 'png') {
imagepng($image);
$mimeType = 'image/png';
} else {
imagejpeg($image, null, $quality);
$mimeType = 'image/jpeg';
}
$normalizedData = ob_get_contents();
ob_end_clean();
imagedestroy($image);
return [
'mime_type' => $mimeType,
'base64' => 'data:' . $mimeType . ';base64,' . base64_encode($normalizedData),
'data' => $normalizedData
];
}
/**
* Process image from URL
*
* @param string $imageUrl URL to download
* @param string $targetFormat Target format
* @param int $quality JPEG quality
* @return array|null Normalized image data or null on failure
*/
private function processImageUrl($imageUrl, $targetFormat = 'jpeg', $quality = 90) {
$context = stream_context_create([
'http' => [
'timeout' => $this->config['timeout'],
'user_agent' => 'WebhookProcessor/1.0'
]
]);
$imageData = @file_get_contents($imageUrl, false, $context);
if ($imageData === false) {
return null;
}
return $this->normalizeImageData($imageData, $targetFormat, $quality);
}
/**
* Process image from file path
*
* @param string $filePath Path to image file
* @param string $targetFormat Target format
* @param int $quality JPEG quality
* @return array|null Normalized image data or null on failure
*/
private function processImageFile($filePath, $targetFormat = 'jpeg', $quality = 90) {
if (!file_exists($filePath)) {
return null;
}
$imageData = file_get_contents($filePath);
if ($imageData === false) {
return null;
}
return $this->normalizeImageData($imageData, $targetFormat, $quality);
}
/**
* Send webhook using cURL
*
* @param array $data Data to send
* @param string $webhookUrl Target URL
* @return bool True on success, false on failure
*/
private function sendWebhook($data, $webhookUrl) {
$ch = curl_init($webhookUrl);
curl_setopt_array($ch, [
CURLOPT_POST => 1,
CURLOPT_POSTFIELDS => json_encode($data),
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_SSL_VERIFYPEER => $this->config['verify_ssl'],
CURLOPT_TIMEOUT => $this->config['timeout'],
CURLOPT_USERAGENT => 'WebhookProcessor/1.0'
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch);
curl_close($ch);
// Log errors if debugging is enabled
if ($httpCode < 200 || $httpCode >= 300 || !empty($curlError)) {
error_log("Webhook failed: HTTP {$httpCode}, Error: {$curlError}, URL: {$webhookUrl}");
return false;
}
return true;
}
/**
* Test webhook endpoint connectivity
*
* @param string $webhookUrl Optional URL to test (uses default if not provided)
* @return array Test results with status, http_code, and response_time
*/
public function testWebhook($webhookUrl = null) {
$webhookUrl = $webhookUrl ?: $this->config['webhook_url'];
$testData = [
'test' => true,
'timestamp' => time(),
'client' => 'WebhookProcessor Test',
'generationType' => 'Test'
];
$startTime = microtime(true);
$ch = curl_init($webhookUrl);
curl_setopt_array($ch, [
CURLOPT_POST => 1,
CURLOPT_POSTFIELDS => json_encode($testData),
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_SSL_VERIFYPEER => $this->config['verify_ssl'],
CURLOPT_TIMEOUT => $this->config['timeout'],
CURLOPT_USERAGENT => 'WebhookProcessor/1.0 Test'
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch);
$responseTime = round((microtime(true) - $startTime) * 1000, 2);
curl_close($ch);
return [
'success' => ($httpCode >= 200 && $httpCode < 300 && empty($curlError)),
'http_code' => $httpCode,
'response_time_ms' => $responseTime,
'error' => $curlError,
'response' => $response
];
}
/**
* Get current configuration
*
* @return array Current configuration (sensitive values masked)
*/
public function getConfig() {
$config = $this->config;
// Mask sensitive webhook URL
if (isset($config['webhook_url'])) {
$config['webhook_url'] = substr($config['webhook_url'], 0, 30) . '...';
}
return $config;
}
/**
* Update configuration
*
* @param array $newConfig New configuration values
* @return void
*/
public function updateConfig($newConfig) {
$this->config = array_merge($this->config, $newConfig);
}
}
/**
* Convenience function for quick webhook sending
*
* @param array $data Generation data
* @param string $webhookUrl Webhook URL
* @param array $config Optional processor configuration
* @return bool Success status
*/
function sendAIWebhook($data, $webhookUrl = null, $config = []) {
if ($webhookUrl) {
$config['webhook_url'] = $webhookUrl;
}
$processor = new WebhookProcessor($config);
return $processor->sendGenerationData($data);
}

View file

@ -1,339 +0,0 @@
<?php
/**
* Audio Webhook Processor for AI Sound Generation Applications
*
* Adapted from the standardized WebhookProcessor for audio files.
* This class provides a standardized way to integrate webhook functionality
* into any application that generates AI audio/sound effects.
*
* Usage:
* require_once 'webhook_processor_audio.php';
* $processor = new AudioWebhookProcessor(['webhook_url' => 'https://your-hook.com']);
* $processor->sendGenerationData($data);
*/
class AudioWebhookProcessor {
private $config;
private $defaultWebhookUrl = 'https://hook.us1.make.celonis.com/sbhcpk0athbdbxxmgijxc5sbwtjsg33h';
/**
* Initialize webhook processor with configuration
*
* @param array $config Configuration options:
* - webhook_url: Target webhook URL (optional)
* - client: Default client name (optional)
* - user_email: Default user email (optional)
* - deliverable_number: Default deliverable number (optional)
* - timeout: cURL timeout in seconds (default: 30)
* - verify_ssl: Whether to verify SSL certificates (default: true)
*/
public function __construct($config = []) {
$this->config = array_merge([
'webhook_url' => $this->defaultWebhookUrl,
'client' => 'Auto-Approved',
'user_email' => 'ai-comp-setup@oliver.agency',
'deliverable_number' => '1000000',
'timeout' => 30,
'verify_ssl' => true
], $config);
}
/**
* Send AI sound generation data to webhook
*
* @param array $data Generation data:
* - prompt: The user's text prompt (required)
* - generation_type: Type of AI generation (ElevenLabs, etc.)
* - settings: Generation settings array (duration, prompt_influence, etc.)
* - audio_data: Raw audio binary data OR audio URL OR file path
* - audio_url: Alternative to audio_data - URL to download audio
* - audio_file: Alternative to audio_data - file path to read audio
* - client: Client name (overrides default)
* - user_email: User email (overrides default)
* - deliverable_number: Deliverable number (overrides default)
* - additional_data: Extra data to include in webhook
* @param string $webhookUrl Optional webhook URL override
* @return bool True on success, false on failure
*/
public function sendGenerationData($data, $webhookUrl = null) {
$webhookUrl = $webhookUrl ?: $this->config['webhook_url'];
// Build standardized webhook data
$webhookData = $this->buildWebhookData($data);
// Send the webhook
return $this->sendWebhook($webhookData, $webhookUrl);
}
/**
* Build standardized webhook data structure for audio
*
* @param array $data Input data
* @return array Standardized webhook data
*/
private function buildWebhookData($data) {
$webhookData = [
'client' => $data['client'] ?? $this->config['client'],
'deliverableNumber' => $data['deliverable_number'] ?? $this->config['deliverable_number'],
'userEmail' => $data['user_email'] ?? $this->config['user_email'],
'generationType' => $data['generation_type'] ?? 'ElevenLabs Sound Effects',
'settings' => $data['settings'] ?? [],
'timestamp' => time(),
];
// Add prompt to settings if provided
if (isset($data['prompt'])) {
$webhookData['settings']['prompt'] = $data['prompt'];
}
// Process audio data if provided
if (isset($data['audio_data'])) {
$normalized = $this->normalizeAudioData($data['audio_data']);
if ($normalized) {
$webhookData['audioFile'] = $normalized['base64'];
$webhookData['audioMimeType'] = $normalized['mime_type'];
}
} elseif (isset($data['audio_url'])) {
$normalized = $this->processAudioUrl($data['audio_url']);
if ($normalized) {
$webhookData['audioFile'] = $normalized['base64'];
$webhookData['audioMimeType'] = $normalized['mime_type'];
}
} elseif (isset($data['audio_file'])) {
$normalized = $this->processAudioFile($data['audio_file']);
if ($normalized) {
$webhookData['audioFile'] = $normalized['base64'];
$webhookData['audioMimeType'] = $normalized['mime_type'];
}
}
// Add any additional data
if (isset($data['additional_data']) && is_array($data['additional_data'])) {
$webhookData = array_merge($webhookData, $data['additional_data']);
}
return $webhookData;
}
/**
* Normalize audio data to standard format
*
* @param mixed $audioData Raw binary data, base64 string, or data URI
* @return array|null Normalized audio data or null on failure
*/
private function normalizeAudioData($audioData) {
// If already a data URI, extract the data
if (is_string($audioData) && strpos($audioData, 'data:') === 0) {
$parts = explode(',', $audioData, 2);
if (count($parts) === 2) {
$mimeType = explode(';', explode(':', $parts[0])[1])[0];
$audioData = base64_decode($parts[1]);
}
}
// If base64 encoded string, decode it
elseif (is_string($audioData) && base64_decode($audioData, true) !== false) {
$audioData = base64_decode($audioData);
}
// Detect mime type
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mimeType = $finfo->buffer($audioData);
// Validate that it's an audio file
if (strpos($mimeType, 'audio/') !== 0) {
// Try common audio extensions based on magic bytes
$audioTypes = [
'audio/mpeg' => ["\xFF\xFB", "\xFF\xF3", "\xFF\xF2"], // MP3
'audio/wav' => ["RIFF"], // WAV
'audio/ogg' => ["OggS"], // OGG
'audio/mp4' => ["\x00\x00\x00"], // M4A/MP4
];
foreach ($audioTypes as $type => $signatures) {
foreach ($signatures as $signature) {
if (strpos($audioData, $signature) === 0) {
$mimeType = $type;
break 2;
}
}
}
}
return [
'mime_type' => $mimeType,
'base64' => 'data:' . $mimeType . ';base64,' . base64_encode($audioData),
'data' => $audioData,
'size' => strlen($audioData)
];
}
/**
* Process audio from URL
*
* @param string $audioUrl URL to download
* @return array|null Normalized audio data or null on failure
*/
private function processAudioUrl($audioUrl) {
$context = stream_context_create([
'http' => [
'timeout' => $this->config['timeout'],
'user_agent' => 'AudioWebhookProcessor/1.0'
]
]);
$audioData = @file_get_contents($audioUrl, false, $context);
if ($audioData === false) {
return null;
}
return $this->normalizeAudioData($audioData);
}
/**
* Process audio from file path
*
* @param string $filePath Path to audio file
* @return array|null Normalized audio data or null on failure
*/
private function processAudioFile($filePath) {
if (!file_exists($filePath)) {
return null;
}
$audioData = file_get_contents($filePath);
if ($audioData === false) {
return null;
}
return $this->normalizeAudioData($audioData);
}
/**
* Send webhook using cURL
*
* @param array $data Data to send
* @param string $webhookUrl Target URL
* @return bool True on success, false on failure
*/
private function sendWebhook($data, $webhookUrl) {
$ch = curl_init($webhookUrl);
curl_setopt_array($ch, [
CURLOPT_POST => 1,
CURLOPT_POSTFIELDS => json_encode($data),
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_SSL_VERIFYPEER => $this->config['verify_ssl'],
CURLOPT_TIMEOUT => $this->config['timeout'],
CURLOPT_USERAGENT => 'AudioWebhookProcessor/1.0'
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch);
curl_close($ch);
// Log errors if debugging is enabled
if ($httpCode < 200 || $httpCode >= 300 || !empty($curlError)) {
error_log("Audio Webhook failed: HTTP {$httpCode}, Error: {$curlError}, URL: {$webhookUrl}");
return false;
}
return true;
}
/**
* Test webhook endpoint connectivity
*
* @param string $webhookUrl Optional URL to test (uses default if not provided)
* @return array Test results with status, http_code, and response_time
*/
public function testWebhook($webhookUrl = null) {
$webhookUrl = $webhookUrl ?: $this->config['webhook_url'];
$testData = [
'test' => true,
'timestamp' => time(),
'client' => 'AudioWebhookProcessor Test',
'generationType' => 'ElevenLabs Sound Effects Test',
'settings' => [
'prompt' => 'Test sound generation',
'duration_seconds' => 5.0,
'prompt_influence' => 0.3
]
];
$startTime = microtime(true);
$ch = curl_init($webhookUrl);
curl_setopt_array($ch, [
CURLOPT_POST => 1,
CURLOPT_POSTFIELDS => json_encode($testData),
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_SSL_VERIFYPEER => $this->config['verify_ssl'],
CURLOPT_TIMEOUT => $this->config['timeout'],
CURLOPT_USERAGENT => 'AudioWebhookProcessor/1.0 Test'
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch);
$responseTime = round((microtime(true) - $startTime) * 1000, 2);
curl_close($ch);
return [
'success' => ($httpCode >= 200 && $httpCode < 300 && empty($curlError)),
'http_code' => $httpCode,
'response_time_ms' => $responseTime,
'error' => $curlError,
'response' => $response
];
}
/**
* Get current configuration
*
* @return array Current configuration (sensitive values masked)
*/
public function getConfig() {
$config = $this->config;
// Mask sensitive webhook URL
if (isset($config['webhook_url'])) {
$config['webhook_url'] = substr($config['webhook_url'], 0, 30) . '...';
}
return $config;
}
/**
* Update configuration
*
* @param array $newConfig New configuration values
* @return void
*/
public function updateConfig($newConfig) {
$this->config = array_merge($this->config, $newConfig);
}
}
/**
* Convenience function for quick audio webhook sending
*
* @param array $data Generation data
* @param string $webhookUrl Webhook URL
* @param array $config Optional processor configuration
* @return bool Success status
*/
function sendAudioWebhook($data, $webhookUrl = null, $config = []) {
if ($webhookUrl) {
$config['webhook_url'] = $webhookUrl;
}
$processor = new AudioWebhookProcessor($config);
return $processor->sendGenerationData($data);
}
?>