music-generation/index.php
DJP ddd35655da 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 <noreply@anthropic.com>
2025-08-19 10:11:07 -04:00

734 lines
29 KiB
PHP

<!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 -->
<div id="login-container">
<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 -->
<button id="logout-button" onclick="signOut()" title="Log Out">
Log Out
</button>
<!-- Dark Mode Toggle Button -->
<button id="darkModeToggle" class="dark-mode-toggle" title="Toggle Dark Mode">
<span id="lightModeIcon">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16">
<path d="M8 11a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0 1a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z"/>
</svg>
</span>
<span id="darkModeIcon" style="display: none;">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16">
<path d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278z"/>
</svg>
</span>
</button>
<!-- Protected Content -->
<div id="protected-content">
<div class="container">
<h1>🎵 Music Generator</h1>
<?php
// Load configuration
$config = require_once 'config.php';
// Include webhook processor
require_once 'webhook_processor_audio.php';
// Clean up old files on page load
cleanOldFiles($config['generated_files_dir'], $config['max_file_age_hours']);
$result = '';
$error = '';
$audioFile = '';
function cleanOldFiles($directory, $maxAgeHours) {
if (!is_dir($directory)) {
return;
}
$files = glob($directory . '*.mp3');
$cutoffTime = time() - ($maxAgeHours * 3600);
foreach ($files as $file) {
if (is_file($file) && filemtime($file) < $cutoffTime) {
unlink($file);
}
}
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$text = $_POST['text'] ?? '';
$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,
'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;
}
}
?>
<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);
showProtectedContent();
}).catch(error => {
console.error("Error during login:", error);
document.getElementById('login-container').style.display = 'flex';
document.getElementById('protected-content').style.display = 'none';
});
}
function signOut() {
sessionStorage.removeItem('accessToken');
console.log("User logged out.");
document.getElementById('login-container').style.display = 'flex';
document.getElementById('protected-content').style.display = 'none';
document.getElementById('logout-button').style.display = 'none';
}
function showProtectedContent() {
const accessToken = sessionStorage.getItem('accessToken');
if (accessToken) {
document.getElementById('login-container').style.display = 'none';
document.getElementById('protected-content').style.display = 'block';
document.getElementById('logout-button').style.display = 'block';
}
}
// Initialize authentication on page load
document.addEventListener('DOMContentLoaded', function() {
// Disable SSO for local testing - auto show protected content
showProtectedContent();
// 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>