commit ddd35655da90f055282bf234c7b683bf07f3ba0b Author: DJP Date: Tue Aug 19 10:11:07 2025 -0400 Convert ElevenLabs Sound Effects to Music Generation API - Updated API endpoints from sound-generation to music endpoints - Added simple prompt music generation mode - Added advanced mode with composition plan generation - Disabled SSO login for local testing - Updated UI to reflect music generation instead of sound effects - Created separate endpoint for composition plan generation - Updated webhook tracking for music generation 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude diff --git a/config.php b/config.php new file mode 100644 index 0000000..9cc9346 --- /dev/null +++ b/config.php @@ -0,0 +1,10 @@ + 'sk_1295b1e2ed87f7cb811484c29cf295dcd80990020d658e02', + 'max_file_age_hours' => 24, // Auto-delete generated files older than this + 'generated_files_dir' => 'generated/' +]; +?> \ No newline at end of file diff --git a/generate_plan.php b/generate_plan.php new file mode 100644 index 0000000..8115672 --- /dev/null +++ b/generate_plan.php @@ -0,0 +1,86 @@ + 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]; + } +} +?> \ No newline at end of file diff --git a/index.php b/index.php new file mode 100644 index 0000000..1f5d4ad --- /dev/null +++ b/index.php @@ -0,0 +1,734 @@ + + + + + + ElevenLabs Music Generator + + + + + + + + + +
+

🎵 Music Generator

+

Please log in to access the music generator and create amazing compositions for your projects.

+ +
+ + + + + + + + +
+
+

🎵 Music Generator

+ + $prompt, + 'model_id' => 'music_v1' + ]; + + if (!empty($musicLengthMs) && is_numeric($musicLengthMs) && $musicLengthMs >= 10000) { + $data['music_length_ms'] = (int)$musicLengthMs; + } + + 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); + + 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(); + + $webhookData = [ + 'prompt' => $prompt, + 'generation_type' => $generationType, + 'settings' => $data, + 'audio_data' => $response, + 'client' => 'Oliver Agency', + 'user_email' => 'music-generation@oliver.agency', + 'deliverable_number' => '1000000', + 'additional_data' => [ + 'filename' => $filename, + 'file_size' => strlen($response), + 'generation_timestamp' => date('Y-m-d H:i:s'), + 'api_endpoint' => 'elevenlabs-music' + ] + ]; + + $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 { + $errorResponse = json_decode($response, true); + $error = 'API Error (' . $httpCode . '): ' . ($errorResponse['detail'] ?? 'Unknown error occurred'); + return false; + } + } + ?> + +
+ +
+ + +
Simple mode uses just a prompt, Advanced mode creates a detailed composition plan first
+
+ +
+ + +
Describe the music you want to generate (max 2000 characters)
+
+ +
+
+ + Auto +
+
+ +
+
0 = Auto duration, 10-300 seconds for manual control
+
+ + + + +
+ + +
+ Error: +
+ + + +
+ +
+ +

+ Download Music Track +
+
+ +
+
+ + + + diff --git a/webhook_processor_audio.php b/webhook_processor_audio.php new file mode 100644 index 0000000..8600d58 --- /dev/null +++ b/webhook_processor_audio.php @@ -0,0 +1,339 @@ + '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); +} +?> \ No newline at end of file