Compare commits

..

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

10 changed files with 1638 additions and 50 deletions

50
.gitignore vendored
View file

@ -1,50 +0,0 @@
# 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

333
README.md Normal file
View file

@ -0,0 +1,333 @@
# 🎵 ElevenLabs Music Generator
A web-based application for generating AI music using ElevenLabs' Music Generation API. This application provides both simple prompt-based music generation and advanced composition planning features.
## 🌟 Features
### 🎼 Two Generation Modes
- **Simple Mode**: Generate music directly from a text prompt
- **Advanced Mode**: Create detailed composition plans first, then generate music from those plans
### 🎛️ Advanced Controls
- **Duration Control**: Set music length from 10 seconds to 5 minutes (300 seconds)
- **Composition Planning**: Generate structured plans with styles, sections, and arrangements
- **Real-time Preview**: Built-in audio player with download capability
- **Dark Mode**: Toggle between light and dark themes
### 🔒 Enterprise Features
- **SSO Authentication**: Microsoft Azure AD integration (disabled for local development)
- **Webhook Integration**: Automatic provenance tracking for generated content
- **Auto-cleanup**: Generated files automatically deleted after 24 hours
- **Error Handling**: Comprehensive error reporting and logging
## 📋 Requirements
### System Requirements
- **PHP 7.4+** with cURL extension
- **Web Server** (Apache, Nginx, or PHP built-in server)
- **ElevenLabs API Key** with Music Generation access
### ElevenLabs Subscription Requirements
- **Music Generation Access**: Requires special access and additional terms acceptance
- **Recommended**: Creator or Pro tier subscription
- **Contact**: Your ElevenLabs account team for music generation enablement
## 🚀 Installation
### 1. Clone the Repository
```bash
git clone https://bitbucket.org/zlalani/music-generation.git
cd music-generation
```
### 2. Configure API Key
Edit `config.php` and replace the API key:
```php
return [
'elevenlabs_api_key' => 'your-actual-api-key-here',
'max_file_age_hours' => 24,
'generated_files_dir' => 'generated/'
];
```
### 3. Set Permissions
```bash
# Create generated files directory
mkdir -p generated/
chmod 755 generated/
# Ensure PHP can write to the directory
chown www-data:www-data generated/ # On Ubuntu/Debian
# OR
chown apache:apache generated/ # On CentOS/RHEL
```
### 4. Local Development
For quick local testing:
```bash
# Using PHP built-in server
php -S localhost:8000
# Then visit: http://localhost:8000
```
## 🎯 Usage Guide
### Simple Mode
1. Select **"Simple Mode"** from the dropdown
2. Enter your music description (e.g., "Upbeat electronic dance track with driving bass")
3. Optionally set duration using the slider (0 = auto-duration)
4. Click **"Generate Music"**
5. Wait for generation (may take 30-60 seconds)
6. Play and download your generated music
### Advanced Mode
1. Select **"Advanced Mode (with Composition Plan)"**
2. Enter your music description
3. Click **"Generate Composition Plan"** first
4. Review the generated plan (includes styles, sections, structure)
5. Click **"Generate Music"** to create audio from the plan
6. Play and download your generated music
### Example Prompts
- **Electronic**: "Energetic EDM track with heavy bass drops and soaring synths"
- **Classical**: "Peaceful piano melody with subtle string accompaniment"
- **Ambient**: "Atmospheric soundscape with ethereal pads and gentle rain sounds"
- **Rock**: "Driving rock anthem with powerful drums and electric guitar"
## 🔧 API Endpoints Used
### Music Generation
- **Endpoint**: `POST https://api.elevenlabs.io/v1/music`
- **Purpose**: Generate music from prompts or composition plans
- **Parameters**:
- `prompt`: Text description (max 2000 characters)
- `music_length_ms`: Duration in milliseconds (10000-300000)
- `composition_plan`: Advanced structured plan (optional)
### Composition Planning
- **Endpoint**: `POST https://api.elevenlabs.io/v1/music/plan`
- **Purpose**: Create detailed composition plans
- **Parameters**:
- `prompt`: Text description (max 2000 characters)
- `music_length_ms`: Duration in milliseconds (optional)
- **Note**: This endpoint doesn't consume credits but is rate-limited
## 🏗️ Architecture
### File Structure
```
music-generation/
├── index.php # Main application interface
├── config.php # Configuration settings
├── generate_plan.php # Composition plan generation endpoint
├── webhook_processor_audio.php # Webhook integration for provenance
├── store_user_session.php # SSO user session storage
├── clear_user_session.php # SSO user session cleanup
├── generated/ # Auto-generated music files (auto-cleanup)
└── README.md # This documentation
```
### Key Components
#### Authentication System
- **Microsoft SSO**: Azure AD integration for enterprise use
- **Local Override**: Disabled for development/testing
- **Session Management**: Token-based authentication
#### Webhook Integration
- **Provenance Tracking**: Automatic logging of all generation events
- **User Tracking**: Captures SSO user information (email, name, ID)
- **Enhanced Metadata**: Generation mode, IP address, user agent
- **Data Structure**: Standardized webhook payload format
- **Error Handling**: Graceful fallback if webhook fails
#### File Management
- **Auto-naming**: Timestamped filename generation
- **Auto-cleanup**: Configurable file retention (default: 24 hours)
- **Storage**: Local filesystem with organized structure
## 🛠️ Configuration Options
### config.php Settings
```php
return [
// ElevenLabs API configuration
'elevenlabs_api_key' => 'your-api-key',
// File management
'max_file_age_hours' => 24, # Auto-delete after 24 hours
'generated_files_dir' => 'generated/' # Storage directory
];
```
### Webhook Configuration
The `AudioWebhookProcessor` class supports:
- Custom webhook URLs
- Configurable timeouts
- SSL verification settings
- Client/user identification
- Deliverable tracking
## 🚨 Troubleshooting
### Common Issues
#### "Music generation requires additional access"
- **Cause**: ElevenLabs Music API requires special access
- **Solution**: Contact your ElevenLabs account team
- **Links**: [Music Terms](https://elevenlabs.io/music-terms)
#### "Please configure your ElevenLabs API key"
- **Cause**: Invalid or missing API key in config.php
- **Solution**: Update config.php with valid API key
#### "API Error (403): limited_access"
- **Cause**: API key doesn't have music generation permissions
- **Solution**: Upgrade subscription and accept music terms
#### Files not generating
- **Check**: PHP error logs for detailed error messages
- **Verify**: Generated directory permissions (755)
- **Ensure**: Sufficient disk space available
### Debug Mode
Enable PHP error reporting for development:
```php
// Add to top of index.php for debugging
error_reporting(E_ALL);
ini_set('display_errors', 1);
```
## 🔐 Security Considerations
### API Key Protection
- Store API keys securely in config.php
- Never commit API keys to version control
- Use environment variables in production
### File Security
- Generated files are automatically cleaned up
- No sensitive data stored in generated files
- Directory permissions properly configured
### SSO Integration
- Microsoft Azure AD integration available
- Token-based session management
- Configurable for enterprise environments
## 🚀 Deployment
### Production Checklist
- [ ] Configure proper web server (Apache/Nginx)
- [ ] Set up HTTPS/SSL certificates
- [ ] Configure proper file permissions
- [ ] Enable SSO authentication
- [ ] Set up monitoring and logging
- [ ] Configure webhook endpoints
- [ ] Test music generation access
- [ ] Set up automated backups
### Environment Variables
Consider using environment variables for sensitive config:
```php
// In config.php
return [
'elevenlabs_api_key' => $_ENV['ELEVENLABS_API_KEY'] ?? 'fallback-key',
// ... other settings
];
```
## 📊 Monitoring & Analytics
### Webhook Tracking
All generations are tracked via webhook with comprehensive metadata:
#### Standard Fields
- **Generation timestamp**: When the music was created
- **User identification**: SSO email, name, and user ID
- **Prompt/settings used**: Full generation parameters
- **File information**: Size, filename, duration
- **Success/failure status**: Generation outcome
#### Enhanced Tracking (Music Specific)
- **Content type**: "Music Generation"
- **Generation mode**: "Simple Prompt" or "Advanced (with Composition Plan)"
- **Client identification**: "Oliver Agency - Music Generation"
- **Technical metadata**: User agent, IP address
- **API endpoint**: "elevenlabs-music" for identification
#### Webhook Payload Example
```json
{
"prompt": "Upbeat electronic dance track",
"generation_type": "ElevenLabs Music Generation",
"client": "Oliver Agency - Music Generation",
"user_email": "user@oliver.agency",
"user_name": "John Doe",
"settings": {...},
"additional_data": {
"content_type": "Music Generation",
"generation_mode": "Simple Prompt",
"filename": "music_2023-08-19_14-30-45.mp3",
"file_size": 2048576,
"ip_address": "192.168.1.100",
"user_agent": "Mozilla/5.0..."
}
}
```
### File Management
- Automatic cleanup prevents disk space issues
- Configurable retention periods
- File size and generation tracking
## 🤝 Contributing
### Development Setup
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Test thoroughly
5. Submit a pull request
### Code Standards
- Follow PHP PSR standards
- Comment complex functionality
- Test with both generation modes
- Verify webhook integration
## 📄 License
This project is proprietary software developed for Oliver Agency. All rights reserved.
## 🆘 Support
### For Technical Issues
- Check the troubleshooting section above
- Review PHP error logs
- Test with the provided example prompts
### For ElevenLabs API Issues
- Contact ElevenLabs support
- Review your subscription tier
- Check API key permissions
### For Feature Requests
- Submit issues to the repository
- Include detailed requirements
- Provide use case examples
## 🔄 Version History
### v1.0.0 - Initial Release
- Music generation with simple and advanced modes
- Composition plan generation
- Webhook integration for provenance tracking
- Dark mode support
- SSO authentication system
- Automatic file cleanup
---
**Built with ❤️ using ElevenLabs Music Generation API**

19
clear_user_session.php Normal file
View file

@ -0,0 +1,19 @@
<?php
// Clear SSO user session data
session_start();
// Clear all SSO-related session data
unset($_SESSION['sso_user_email']);
unset($_SESSION['sso_user_name']);
unset($_SESSION['sso_user_id']);
unset($_SESSION['sso_login_timestamp']);
// Log session cleared
error_log("SSO user session cleared");
echo json_encode([
'success' => true,
'message' => 'User session cleared successfully'
]);
?>

10
config.php Normal file
View file

@ -0,0 +1,10 @@
<?php
// Configuration file for ElevenLabs Music Generator
// IMPORTANT: Replace the API key below with your new ElevenLabs Music API key
return [
'elevenlabs_api_key' => 'sk_1295b1e2ed87f7cb811484c29cf295dcd80990020d658e02',
'max_file_age_hours' => 24, // Auto-delete generated files older than this
'generated_files_dir' => 'generated/'
];
?>

86
generate_plan.php Normal file
View file

@ -0,0 +1,86 @@
<?php
// Composition Plan Generation Endpoint for ElevenLabs Music API
header('Content-Type: application/json');
// Load configuration
$config = require_once 'config.php';
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['success' => false, 'error' => 'Method not allowed']);
exit;
}
$prompt = $_POST['prompt'] ?? '';
$musicLengthMs = (int)($_POST['music_length_ms'] ?? 0);
$apiKey = $config['elevenlabs_api_key'];
if (empty($prompt)) {
echo json_encode(['success' => false, 'error' => 'Prompt is required']);
exit;
}
if (empty($apiKey) || $apiKey === 'your-api-key-here') {
echo json_encode(['success' => false, 'error' => 'ElevenLabs API key not configured']);
exit;
}
// Generate composition plan
$planResult = generateCompositionPlan($prompt, $musicLengthMs, $apiKey);
if ($planResult['success']) {
echo json_encode(['success' => true, 'plan' => $planResult['plan']]);
} else {
echo json_encode(['success' => false, 'error' => $planResult['error']]);
}
function generateCompositionPlan($prompt, $musicLengthMs, $apiKey) {
$url = 'https://api.elevenlabs.io/v1/music/plan';
$data = [
'prompt' => $prompt,
'model_id' => 'music_v1'
];
// Add music length if specified (minimum 10 seconds)
if ($musicLengthMs >= 10000) {
$data['music_length_ms'] = $musicLengthMs;
}
$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)) {
curl_close($ch);
return ['success' => false, 'error' => 'Connection error: ' . curl_error($ch)];
}
curl_close($ch);
if ($httpCode === 200) {
$planData = json_decode($response, true);
if ($planData) {
return ['success' => true, 'plan' => $planData];
} else {
return ['success' => false, 'error' => 'Invalid response format'];
}
} else {
$errorResponse = json_decode($response, true);
$errorMessage = $errorResponse['detail'] ?? 'Unknown error occurred';
return ['success' => false, 'error' => 'API Error (' . $httpCode . '): ' . $errorMessage];
}
}
?>

Binary file not shown.

Binary file not shown.

805
index.php Normal file
View file

@ -0,0 +1,805 @@
<?php
// Start session for SSO user tracking
session_start();
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ElevenLabs Music 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 - Hidden for local testing -->
<div id="login-container" style="display: none;">
<h1>🎵 Music Generator</h1>
<p>Please log in to access the music generator and create amazing compositions for your projects.</p>
<button id="login-button" onclick="signIn()">
Log In with Oliver SSO
</button>
</div>
<!-- Logout Button - Hidden for local testing -->
<button id="logout-button" onclick="signOut()" title="Log Out" style="display: none;">
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>🎵 Music Generator</h1>
<div class="result" style="background-color: #fff3cd; border: 1px solid #ffeaa7; color: #856404; margin-bottom: 20px;">
<strong>Note:</strong> ElevenLabs Music Generation requires special access and additional terms acceptance.
Contact your account team to enable music generation access.
<a href="https://elevenlabs.io/music-terms" target="_blank">Learn more about music terms</a>
</div>
<?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'] ?? '';
$mode = $_POST['mode'] ?? 'simple';
$musicLengthMs = ($_POST['music_length_ms'] ?? 0) * 1000; // Convert seconds to milliseconds
$compositionPlan = $_POST['composition_plan'] ?? null;
$apiKey = $config['elevenlabs_api_key'];
if (empty($text)) {
$error = 'Please enter a description for your music.';
} elseif (empty($apiKey) || $apiKey === 'your-api-key-here') {
$error = 'Please configure your ElevenLabs API key in config.php';
} else {
if ($mode === 'advanced' && !empty($compositionPlan)) {
$audioFile = generateMusicWithPlan($compositionPlan, $apiKey, $error);
} else {
$audioFile = generateMusic($text, $musicLengthMs, $apiKey, $error);
}
if ($audioFile) {
$result = 'Music generated successfully!';
}
}
}
function generateMusic($prompt, $musicLengthMs, $apiKey, &$error) {
$url = 'https://api.elevenlabs.io/v1/music';
$data = [
'prompt' => $prompt
];
// Only add music_length_ms if it's specified and valid (minimum 10 seconds)
if (!empty($musicLengthMs) && is_numeric($musicLengthMs) && $musicLengthMs >= 10000) {
$data['music_length_ms'] = (int)$musicLengthMs;
}
// Don't include model_id as it defaults to music_v1
return generateMusicRequest($url, $data, $apiKey, $error, 'ElevenLabs Music Generation', $prompt);
}
function generateMusicWithPlan($compositionPlan, $apiKey, &$error) {
$url = 'https://api.elevenlabs.io/v1/music';
$data = [
'composition_plan' => json_decode($compositionPlan, true),
'model_id' => 'music_v1'
];
return generateMusicRequest($url, $data, $apiKey, $error, 'ElevenLabs Music Generation (Advanced)', 'Advanced composition');
}
function generateMusicRequest($url, $data, $apiKey, &$error, $generationType, $prompt) {
$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, 120); // Increased timeout for music generation
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
// Log the request for debugging
error_log("Music API Request - URL: $url, Data: " . json_encode($data) . ", HTTP Code: $httpCode, Response: " . substr($response, 0, 500));
if (curl_error($ch)) {
$error = 'Connection error: ' . curl_error($ch);
curl_close($ch);
return false;
}
curl_close($ch);
if ($httpCode === 200) {
$filename = 'music_' . 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();
// Get user info from SSO session if available
$userEmail = 'music-generation@oliver.agency'; // Default
$userName = 'Anonymous User'; // Default
// Check for SSO session data (when SSO is enabled)
if (isset($_SESSION['sso_user_email'])) {
$userEmail = $_SESSION['sso_user_email'];
}
if (isset($_SESSION['sso_user_name'])) {
$userName = $_SESSION['sso_user_name'];
}
$webhookData = [
'prompt' => $prompt,
'generation_type' => $generationType,
'settings' => $data,
'audio_data' => $response,
'client' => 'Oliver Agency - Music Generation',
'user_email' => $userEmail,
'user_name' => $userName,
'deliverable_number' => '1000000',
'additional_data' => [
'filename' => $filename,
'file_size' => strlen($response),
'generation_timestamp' => date('Y-m-d H:i:s'),
'api_endpoint' => 'elevenlabs-music',
'content_type' => 'Music Generation',
'generation_mode' => isset($data['composition_plan']) ? 'Advanced (with Composition Plan)' : 'Simple Prompt',
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'Unknown',
'ip_address' => $_SERVER['REMOTE_ADDR'] ?? 'Unknown'
]
];
$webhookSuccess = $webhookProcessor->sendGenerationData($webhookData);
if (!$webhookSuccess) {
error_log("Webhook failed for music generation: " . $filename);
}
} catch (Exception $e) {
error_log("Webhook error: " . $e->getMessage());
}
return 'generated/' . $filename;
} else {
$error = 'Failed to save the generated music file.';
return false;
}
} else {
// Better error handling
$errorResponse = json_decode($response, true);
if (json_last_error() === JSON_ERROR_NONE && is_array($errorResponse)) {
// Successfully parsed JSON response
if (isset($errorResponse['detail'])) {
$errorDetail = is_array($errorResponse['detail']) ? json_encode($errorResponse['detail']) : $errorResponse['detail'];
} elseif (isset($errorResponse['message'])) {
$errorDetail = is_array($errorResponse['message']) ? json_encode($errorResponse['message']) : $errorResponse['message'];
} elseif (isset($errorResponse['error'])) {
$errorDetail = is_array($errorResponse['error']) ? json_encode($errorResponse['error']) : $errorResponse['error'];
} else {
$errorDetail = json_encode($errorResponse);
}
} else {
// Raw response if JSON parsing failed
$errorDetail = substr($response, 0, 500);
}
$error = 'API Error (' . $httpCode . '): ' . $errorDetail;
return false;
}
}
?>
<form method="POST" id="musicForm">
<div class="form-group">
<label for="mode">Generation Mode:</label>
<select id="mode" name="mode" onchange="toggleMode()">
<option value="simple" <?php echo ($_POST['mode'] ?? 'simple') === 'simple' ? 'selected' : ''; ?>>Simple Mode</option>
<option value="advanced" <?php echo ($_POST['mode'] ?? 'simple') === 'advanced' ? 'selected' : ''; ?>>Advanced Mode (with Composition Plan)</option>
</select>
<div class="help-text">Simple mode uses just a prompt, Advanced mode creates a detailed composition plan first</div>
</div>
<div class="form-group">
<label for="text">Music Description:</label>
<textarea id="text" name="text" placeholder="e.g., Upbeat electronic dance track with driving bass and soaring synths" required><?php echo htmlspecialchars($_POST['text'] ?? ''); ?></textarea>
<div class="help-text">Describe the music you want to generate (max 2000 characters)</div>
</div>
<div class="form-group">
<div class="slider-label">
<label for="music_length_ms">Duration (seconds):</label>
<span class="slider-value" id="duration_value">Auto</span>
</div>
<div class="slider-container">
<input type="range" id="music_length_ms" name="music_length_ms" class="slider" min="0" max="300" step="1" value="<?php echo htmlspecialchars(($_POST['music_length_ms'] ?? 0) / 1000); ?>">
</div>
<div class="help-text">0 = Auto duration, 10-300 seconds for manual control</div>
</div>
<div id="advanced-options" style="display: none;">
<div class="form-group">
<label>Step 1: Generate Composition Plan</label>
<button type="button" id="generatePlanBtn" onclick="generateCompositionPlan()">Generate Composition Plan</button>
<div class="help-text">Creates a detailed plan including styles, sections, and structure</div>
</div>
<div id="composition-plan-result" style="display: none;">
<div class="form-group">
<label for="composition_plan">Generated Composition Plan:</label>
<textarea id="composition_plan" name="composition_plan" readonly style="height: 200px;"></textarea>
</div>
</div>
</div>
<button type="submit" id="generateBtn">Generate Music</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 Music Track</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: "https://ai-sandbox.oliver.solutions/format"
},
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);
// Store user information for webhook tracking
const userInfo = {
email: loginResponse.account.username,
name: loginResponse.account.name || loginResponse.account.username,
userId: loginResponse.account.localAccountId
};
// Send user info to server for session storage
fetch('store_user_session.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(userInfo)
});
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.");
// Clear server-side session
fetch('clear_user_session.php', { method: 'POST' });
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() {
// Disable SSO for local testing - directly show protected content
document.getElementById('login-container').style.display = 'none';
document.getElementById('protected-content').style.display = 'block';
document.getElementById('logout-button').style.display = 'none';
// 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('music_length_ms');
const durationValue = document.getElementById('duration_value');
function updateDurationValue() {
const value = parseFloat(durationSlider.value);
durationValue.textContent = value === 0 ? 'Auto' : value + 's';
}
function toggleMode() {
const mode = document.getElementById('mode').value;
const advancedOptions = document.getElementById('advanced-options');
if (mode === 'advanced') {
advancedOptions.style.display = 'block';
} else {
advancedOptions.style.display = 'none';
}
}
function generateCompositionPlan() {
const prompt = document.getElementById('text').value;
const musicLengthMs = document.getElementById('music_length_ms').value * 1000;
const generatePlanBtn = document.getElementById('generatePlanBtn');
if (!prompt.trim()) {
alert('Please enter a music description first.');
return;
}
generatePlanBtn.disabled = true;
generatePlanBtn.textContent = 'Generating Plan...';
// Make AJAX request to generate composition plan
const xhr = new XMLHttpRequest();
xhr.open('POST', 'generate_plan.php', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
generatePlanBtn.disabled = false;
generatePlanBtn.textContent = 'Generate Composition Plan';
if (xhr.status === 200) {
try {
const response = JSON.parse(xhr.responseText);
if (response.success) {
document.getElementById('composition_plan').value = JSON.stringify(response.plan, null, 2);
document.getElementById('composition-plan-result').style.display = 'block';
} else {
alert('Error generating plan: ' + response.error);
}
} catch (e) {
alert('Error parsing response');
}
} else {
alert('Error generating composition plan');
}
}
};
const params = 'prompt=' + encodeURIComponent(prompt) +
'&music_length_ms=' + encodeURIComponent(musicLengthMs);
xhr.send(params);
}
durationSlider.addEventListener('input', updateDurationValue);
// Initialize values on page load
updateDurationValue();
toggleMode(); // Initialize mode display
document.getElementById('musicForm').addEventListener('submit', function() {
const btn = document.getElementById('generateBtn');
btn.disabled = true;
btn.textContent = 'Generating Music... Please wait';
});
</script>
</body>
</html>

46
store_user_session.php Normal file
View file

@ -0,0 +1,46 @@
<?php
// Store SSO user information in PHP session for webhook tracking
session_start();
// Only accept POST requests
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['success' => false, 'error' => 'Method not allowed']);
exit;
}
// Get JSON input
$input = file_get_contents('php://input');
$userData = json_decode($input, true);
if (!$userData) {
http_response_code(400);
echo json_encode(['success' => false, 'error' => 'Invalid JSON data']);
exit;
}
// Store user information in session for webhook usage
if (isset($userData['email'])) {
$_SESSION['sso_user_email'] = $userData['email'];
}
if (isset($userData['name'])) {
$_SESSION['sso_user_name'] = $userData['name'];
}
if (isset($userData['userId'])) {
$_SESSION['sso_user_id'] = $userData['userId'];
}
// Store timestamp of login
$_SESSION['sso_login_timestamp'] = time();
// Log successful session storage
error_log("SSO user session stored: " . $userData['email'] ?? 'Unknown');
echo json_encode([
'success' => true,
'message' => 'User session stored successfully'
]);
?>

339
webhook_processor_audio.php Normal file
View file

@ -0,0 +1,339 @@
<?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);
}
?>