Compare commits
No commits in common. "main" and "master" have entirely different histories.
10 changed files with 1638 additions and 50 deletions
50
.gitignore
vendored
50
.gitignore
vendored
|
|
@ -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
333
README.md
Normal 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
19
clear_user_session.php
Normal 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
10
config.php
Normal 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
86
generate_plan.php
Normal 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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
BIN
generated/audio_2025-08-19_14-34-49.mp3
Normal file
BIN
generated/audio_2025-08-19_14-34-49.mp3
Normal file
Binary file not shown.
BIN
generated/audio_2025-08-19_14-35-14.mp3
Normal file
BIN
generated/audio_2025-08-19_14-35-14.mp3
Normal file
Binary file not shown.
805
index.php
Normal file
805
index.php
Normal 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
46
store_user_session.php
Normal 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
339
webhook_processor_audio.php
Normal 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);
|
||||||
|
}
|
||||||
|
?>
|
||||||
Loading…
Add table
Reference in a new issue