![]() Server : Apache/2 System : Linux server-15-235-50-60 5.15.0-164-generic #174-Ubuntu SMP Fri Nov 14 20:25:16 UTC 2025 x86_64 User : gositeme ( 1004) PHP Version : 8.2.29 Disable Function : exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname Directory : /home/gositeme/domains/soundstudiopro.com/public_html/ |
<?php
// Enable error reporting for debugging
error_reporting(E_ALL);
ini_set('display_errors', 0);
// Set proper headers
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST, GET, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
// Handle preflight requests
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit(0);
}
// Include database configuration
require_once 'config/database.php';
// Log callback data for debugging
$logFile = 'callback_log.txt';
$timestamp = date('Y-m-d H:i:s');
$input = file_get_contents('php://input');
$headers = getallheaders();
// Log the callback
$logEntry = "[$timestamp] Callback received\n";
$logEntry .= "Headers: " . json_encode($headers) . "\n";
$logEntry .= "Body: " . $input . "\n";
$logEntry .= "Method: " . $_SERVER['REQUEST_METHOD'] . "\n";
$logEntry .= "----------------------------------------\n";
file_put_contents($logFile, $logEntry, FILE_APPEND | LOCK_EX);
// Function to properly sanitize and escape text data from DOM
function sanitizeDOMText($text) {
if (empty($text)) return $text;
// Convert to string if it's not already
$text = (string) $text;
// Handle common apostrophe issues from DOM
$text = str_replace(
['\'', '"', ''', '"', ''', '"', '’', '“', '”'],
['\'', '"', '\'', '"', '\'', '"', '\'', '"', '"'],
$text
);
// Remove any HTML entities that might cause issues
$text = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8');
// Clean up any remaining problematic characters
$text = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', '', $text);
// Ensure proper UTF-8 encoding
if (!mb_check_encoding($text, 'UTF-8')) {
$text = mb_convert_encoding($text, 'UTF-8', 'auto');
}
// Trim whitespace
$text = trim($text);
return $text;
}
// Function to sanitize array data recursively
function sanitizeDOMArray($data) {
if (is_array($data)) {
foreach ($data as $key => $value) {
if (is_string($value)) {
$data[$key] = sanitizeDOMText($value);
} elseif (is_array($value)) {
$data[$key] = sanitizeDOMArray($value);
}
}
}
return $data;
}
// Function to extract title from various API response formats
function extractTitleFromCallback($data) {
// Check multiple possible locations in the API response
if (isset($data['title']) && !empty($data['title'])) {
return sanitizeDOMText($data['title']);
}
if (isset($data['data']['title']) && !empty($data['data']['title'])) {
return sanitizeDOMText($data['data']['title']);
}
// Check in data.data array (common API.Box format)
if (isset($data['data']['data']) && is_array($data['data']['data'])) {
foreach ($data['data']['data'] as $item) {
if (isset($item['title']) && !empty($item['title'])) {
return sanitizeDOMText($item['title']);
}
}
}
// Check in direct data array
if (isset($data['data']) && is_array($data['data']) && !isset($data['data']['data'])) {
foreach ($data['data'] as $item) {
if (is_array($item) && isset($item['title']) && !empty($item['title'])) {
return sanitizeDOMText($item['title']);
}
}
}
return null;
}
// Function to extract duration from various API response formats
function extractDurationFromCallback($data) {
// Check multiple possible locations in the API response
if (isset($data['duration']) && $data['duration'] !== null && $data['duration'] !== '') {
return floatval($data['duration']);
}
if (isset($data['data']['duration']) && $data['data']['duration'] !== null && $data['data']['duration'] !== '') {
return floatval($data['data']['duration']);
}
// Check in data.data array (common API.Box format)
if (isset($data['data']['data']) && is_array($data['data']['data'])) {
foreach ($data['data']['data'] as $item) {
if (isset($item['duration']) && $item['duration'] !== null && $item['duration'] !== '') {
return floatval($item['duration']);
}
}
}
// Check in direct data array
if (isset($data['data']) && is_array($data['data']) && !isset($data['data']['data'])) {
foreach ($data['data'] as $item) {
if (is_array($item) && isset($item['duration']) && $item['duration'] !== null && $item['duration'] !== '') {
return floatval($item['duration']);
}
}
}
return null;
}
// Function to extract tags from various API response formats
function extractTagsFromCallback($data) {
// Check multiple possible locations in the API response
if (isset($data['tags']) && !empty($data['tags'])) {
if (is_array($data['tags'])) {
return $data['tags'];
} elseif (is_string($data['tags'])) {
return [$data['tags']];
}
}
if (isset($data['data']['tags']) && !empty($data['data']['tags'])) {
if (is_array($data['data']['tags'])) {
return $data['data']['tags'];
} elseif (is_string($data['data']['tags'])) {
return [$data['data']['tags']];
}
}
// Check in data.data array (common API.Box format)
if (isset($data['data']['data']) && is_array($data['data']['data'])) {
foreach ($data['data']['data'] as $item) {
if (isset($item['tags']) && !empty($item['tags'])) {
if (is_array($item['tags'])) {
return $item['tags'];
} elseif (is_string($item['tags'])) {
return [$item['tags']];
}
}
}
}
// Check in direct data array
if (isset($data['data']) && is_array($data['data']) && !isset($data['data']['data'])) {
foreach ($data['data'] as $item) {
if (is_array($item) && isset($item['tags']) && !empty($item['tags'])) {
if (is_array($item['tags'])) {
return $item['tags'];
} elseif (is_string($item['tags'])) {
return [$item['tags']];
}
}
}
}
return null;
}
// Function to extract model_name from various API response formats
function extractModelNameFromCallback($data) {
// Check multiple possible locations in the API response
if (isset($data['model_name']) && !empty($data['model_name'])) {
return sanitizeDOMText($data['model_name']);
}
if (isset($data['model']) && !empty($data['model'])) {
return sanitizeDOMText($data['model']);
}
if (isset($data['data']['model_name']) && !empty($data['data']['model_name'])) {
return sanitizeDOMText($data['data']['model_name']);
}
if (isset($data['data']['model']) && !empty($data['data']['model'])) {
return sanitizeDOMText($data['data']['model']);
}
// Check in data.data array (common API.Box format)
if (isset($data['data']['data']) && is_array($data['data']['data'])) {
foreach ($data['data']['data'] as $item) {
if (isset($item['model_name']) && !empty($item['model_name'])) {
return sanitizeDOMText($item['model_name']);
}
if (isset($item['model']) && !empty($item['model'])) {
return sanitizeDOMText($item['model']);
}
}
}
// Check in direct data array
if (isset($data['data']) && is_array($data['data']) && !isset($data['data']['data'])) {
foreach ($data['data'] as $item) {
if (is_array($item)) {
if (isset($item['model_name']) && !empty($item['model_name'])) {
return sanitizeDOMText($item['model_name']);
}
if (isset($item['model']) && !empty($item['model'])) {
return sanitizeDOMText($item['model']);
}
}
}
}
return null;
}
// Function to download and store audio files locally
function downloadAndStoreAudio($audioUrl, $taskId, $type = 'main', $variationIndex = null) {
if (empty($audioUrl) || !filter_var($audioUrl, FILTER_VALIDATE_URL)) {
return null;
}
// Create audio storage directory
$audioDir = 'audio_files/';
if (!is_dir($audioDir)) {
mkdir($audioDir, 0755, true);
}
// Generate filename
$extension = pathinfo(parse_url($audioUrl, PHP_URL_PATH), PATHINFO_EXTENSION) ?: 'mp3';
if ($type === 'variation' && $variationIndex !== null) {
$filename = "{$taskId}_variation_{$variationIndex}.{$extension}";
} else {
$filename = "{$taskId}.{$extension}";
}
$localPath = $audioDir . $filename;
$webPath = '/audio_files/' . $filename;
// Download the file
$context = stream_context_create([
'http' => [
'timeout' => 300, // 5 minutes timeout
'user_agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
]
]);
$audioContent = file_get_contents($audioUrl, false, $context);
if ($audioContent === false) {
error_log("Failed to download audio from: $audioUrl");
return null;
}
// Save the file
if (file_put_contents($localPath, $audioContent, LOCK_EX)) {
// Set proper permissions
chmod($localPath, 0644);
// Log the download
$downloadLog = [
'timestamp' => date('Y-m-d H:i:s'),
'action' => 'audio_downloaded',
'task_id' => $taskId,
'original_url' => $audioUrl,
'local_path' => $localPath,
'web_path' => $webPath,
'file_size' => strlen($audioContent),
'type' => $type,
'variation_index' => $variationIndex
];
$downloadLogFile = 'logs/audio_downloads.log';
file_put_contents($downloadLogFile, json_encode($downloadLog) . "\n", FILE_APPEND | LOCK_EX);
return $webPath;
}
return null;
}
// Helper function to get original prompt and title from database
function getTrackOriginalData($taskId) {
$pdo = getDBConnection();
if (!$pdo) return ['prompt' => null, 'title' => null];
try {
$stmt = $pdo->prepare("SELECT prompt, title FROM music_tracks WHERE task_id = ?");
$stmt->execute([$taskId]);
$track = $stmt->fetch(PDO::FETCH_ASSOC);
if ($track) {
return [
'prompt' => $track['prompt'] ?? null,
'title' => $track['title'] ?? null
];
}
} catch (Exception $e) {
error_log("Error fetching original track data: " . $e->getMessage());
}
return ['prompt' => null, 'title' => null];
}
// Function to parse prompt and extract metadata
function parsePromptForMetadata($prompt) {
if (empty($prompt)) return [];
$metadata = [];
$promptLower = strtolower($prompt);
// Extract BPM/Tempo from prompt as FALLBACK (API.box doesn't provide BPM in callbacks)
// We prioritize API BPM if available, but use prompt BPM as fallback since API doesn't provide it
// Validate BPM is in reasonable range (40-300 BPM)
if (preg_match('/bpm[:\s]+(\d+)/i', $prompt, $matches)) {
$bpm = intval($matches[1]);
if ($bpm >= 40 && $bpm <= 300) {
$metadata['bpm'] = $bpm;
}
} elseif (preg_match('/(\d+)\s*bpm/i', $prompt, $matches)) {
$bpm = intval($matches[1]);
if ($bpm >= 40 && $bpm <= 300) {
$metadata['bpm'] = $bpm;
}
} elseif (preg_match('/tempo[:\s]+(\d+)/i', $prompt, $matches)) {
$bpm = intval($matches[1]);
if ($bpm >= 40 && $bpm <= 300) {
$metadata['bpm'] = $bpm;
}
}
// Extract Genre - handle "Style:" prefix and multiple genres separated by "/"
if (preg_match('/style[:\s]+([^:]+?)(?:\s*[:\-]|$)/i', $prompt, $matches)) {
$styleText = trim($matches[1]);
// Split by "/" to get multiple genres
$styleGenres = array_map('trim', explode('/', $styleText));
foreach ($styleGenres as $styleGenre) {
$styleGenre = trim($styleGenre);
if (!empty($styleGenre)) {
// Normalize common variations
$styleGenre = str_replace(['psy ', 'psy-'], 'psy', strtolower($styleGenre));
$metadata['genre'] = ucwords($styleGenre);
break; // Use first genre found
}
}
}
// Also check for genre keywords (expanded list including psytrance, psy chill, etc.)
$genres = ['psytrance', 'psy trance', 'psy-chill', 'psy chill', 'electronic', 'house', 'techno', 'pop', 'hip hop', 'rock', 'jazz', 'classical', 'ambient', 'trance', 'dubstep', 'r&b', 'reggae', 'country', 'folk', 'blues', 'funk', 'disco', 'drum & bass', 'drum and bass', 'progressive', 'chillout', 'lofi', 'lo-fi', 'edm', 'trap', 'dance', 'indie', 'alternative', 'metal', 'punk', 'soul', 'gospel', 'latin', 'world', 'experimental'];
foreach ($genres as $genre) {
if (preg_match('/\b' . preg_quote($genre, '/') . '\b/i', $promptLower)) {
$metadata['genre'] = ucwords($genre);
break;
}
}
// Extract Mood - handle "Theme:" prefix and common mood words
if (preg_match('/theme[:\s]+([^:]+?)(?:\s*[:\-]|$)/i', $prompt, $matches)) {
$themeText = strtolower(trim($matches[1]));
// Check if theme contains mood keywords
$moods = ['happy', 'sad', 'energetic', 'chill', 'relaxing', 'aggressive', 'melancholic', 'uplifting', 'dark', 'bright', 'peaceful', 'intense', 'calm', 'excited', 'romantic', 'mysterious', 'epic', 'dramatic', 'playful', 'serious', 'neutral', 'love', 'unity', 'frequency'];
foreach ($moods as $mood) {
if (strpos($themeText, $mood) !== false) {
// Map theme words to moods
if ($mood === 'love' || $mood === 'romantic') {
$metadata['mood'] = 'Romantic';
} elseif ($mood === 'unity' || $mood === 'peaceful') {
$metadata['mood'] = 'Peaceful';
} elseif ($mood === 'frequency' || $mood === 'energetic') {
$metadata['mood'] = 'Energetic';
} else {
$metadata['mood'] = ucfirst($mood);
}
break;
}
}
}
// Also check for mood keywords directly
$moods = ['happy', 'sad', 'energetic', 'chill', 'relaxing', 'aggressive', 'melancholic', 'uplifting', 'dark', 'bright', 'peaceful', 'intense', 'calm', 'excited', 'romantic', 'mysterious', 'epic', 'dramatic', 'playful', 'serious', 'neutral'];
if (!isset($metadata['mood'])) {
foreach ($moods as $mood) {
if (preg_match('/\b' . preg_quote($mood, '/') . '\b/i', $promptLower)) {
$metadata['mood'] = ucfirst($mood);
break;
}
}
}
// Extract Key - handle "Key:" prefix and formats like "6B – D Major" or "D Major"
// First, try to extract numerical key (e.g., "6B", "1A", "12A")
if (preg_match('/key[:\s]+([0-9]+[A-G]?)\s*[–\-]?\s*([A-G][#b]?\s*(?:major|minor|maj|min))/i', $prompt, $matches)) {
$numericalKey = trim($matches[1]); // e.g., "6B"
$keyText = trim($matches[2]); // e.g., "D Major"
// Normalize
$keyText = preg_replace('/\s+/', ' ', $keyText);
$keyText = str_replace(['maj', 'min'], ['major', 'minor'], strtolower($keyText));
$metadata['key'] = ucwords($keyText);
$metadata['numerical_key'] = $numericalKey; // Store numerical key separately
} elseif (preg_match('/key[:\s]+(?:[0-9]+[A-G]?\s*[–\-]?\s*)?([A-G][#b]?\s*(?:major|minor|maj|min))/i', $prompt, $matches)) {
$keyText = trim($matches[1]);
// Normalize
$keyText = preg_replace('/\s+/', ' ', $keyText);
$keyText = str_replace(['maj', 'min'], ['major', 'minor'], strtolower($keyText));
$metadata['key'] = ucwords($keyText);
} elseif (preg_match('/\b([A-G][#b]?\s*(?:major|minor|maj|min))\b/i', $prompt, $matches)) {
$keyText = trim($matches[1]);
$keyText = preg_replace('/\s+/', ' ', $keyText);
$keyText = str_replace(['maj', 'min'], ['major', 'minor'], strtolower($keyText));
$metadata['key'] = ucwords($keyText);
}
return $metadata;
}
// Function to extract comprehensive metadata
function extractComprehensiveMetadata($data, $originalPrompt = null) {
// API.box callback structure: data.data.data[0] contains the actual track data
// Extract BPM from the nested structure first
$apiBpm = null;
$apiGenre = null;
$apiKey = null;
$apiMood = null;
// Check nested data structure (API.box format)
if (isset($data['data']['data']) && is_array($data['data']['data']) && !empty($data['data']['data'])) {
$trackData = $data['data']['data'][0]; // First variation
$apiBpm = $trackData['bpm'] ?? $trackData['tempo'] ?? null;
$apiGenre = $trackData['genre'] ?? $trackData['tags'] ?? null;
if (is_string($apiGenre)) {
// If tags is a string like "heavy bass, synth melodies...", extract first genre
$tagsArray = explode(',', $apiGenre);
$apiGenre = trim($tagsArray[0]) ?? null;
}
$apiKey = $trackData['key'] ?? $trackData['musical_key'] ?? null;
$apiMood = $trackData['mood'] ?? null;
}
// Fallback to top-level data
if (!$apiBpm) {
$apiBpm = $data['bpm'] ?? $data['tempo'] ?? null;
}
if (!$apiGenre) {
$apiGenre = $data['genre'] ?? (is_array($data['tags'] ?? null) ? ($data['tags'][0] ?? null) : $data['tags']) ?? null;
}
if (!$apiKey) {
$apiKey = $data['key'] ?? $data['musical_key'] ?? null;
}
if (!$apiMood) {
$apiMood = $data['mood'] ?? null;
}
// Log what API returned
$logFile = 'callback_log.txt';
file_put_contents($logFile, "🔍 API Metadata Check:\n", FILE_APPEND | LOCK_EX);
file_put_contents($logFile, " - API Genre: " . ($apiGenre ?: 'NOT PROVIDED') . "\n", FILE_APPEND | LOCK_EX);
file_put_contents($logFile, " - API BPM: " . ($apiBpm ?: 'NOT PROVIDED') . " (type: " . gettype($apiBpm) . ")\n", FILE_APPEND | LOCK_EX);
file_put_contents($logFile, " - API Key: " . ($apiKey ?: 'NOT PROVIDED') . "\n", FILE_APPEND | LOCK_EX);
file_put_contents($logFile, " - API Mood: " . ($apiMood ?: 'NOT PROVIDED') . "\n", FILE_APPEND | LOCK_EX);
// IMPORTANT: Check if we're accidentally using duration as BPM
if (isset($data['data']['data']) && is_array($data['data']['data']) && !empty($data['data']['data'])) {
$trackData = $data['data']['data'][0];
$duration = $trackData['duration'] ?? null;
file_put_contents($logFile, " - ⚠️ Duration from API: " . ($duration ?: 'NOT PROVIDED') . " (this is NOT BPM!)\n", FILE_APPEND | LOCK_EX);
if ($duration && $apiBpm && abs($duration - $apiBpm) < 1) {
file_put_contents($logFile, " - 🚨 ERROR: Duration ($duration) matches BPM ($apiBpm) - this is wrong!\n", FILE_APPEND | LOCK_EX);
}
}
// If API didn't provide values, try parsing the prompt
$parsedMetadata = [];
if ($originalPrompt) {
file_put_contents($logFile, " - Parsing prompt for metadata...\n", FILE_APPEND | LOCK_EX);
$parsedMetadata = parsePromptForMetadata($originalPrompt);
file_put_contents($logFile, " - Parsed from prompt: " . json_encode($parsedMetadata) . "\n", FILE_APPEND | LOCK_EX);
}
// Use API values if available, otherwise use parsed, otherwise use defaults
$finalGenre = $apiGenre ?: $parsedMetadata['genre'] ?? 'Electronic';
// BPM: API.box doesn't provide BPM in callbacks, so we use prompt BPM as fallback
// Priority: API BPM > Prompt BPM > null
$finalBpm = $apiBpm ?? $parsedMetadata['bpm'] ?? null;
$finalKey = $apiKey ?: $parsedMetadata['key'] ?? 'C major';
$finalMood = $apiMood ?: $parsedMetadata['mood'] ?? 'neutral';
// Validate BPM is in reasonable range (40-300 BPM)
if ($finalBpm !== null) {
$finalBpm = intval($finalBpm);
if ($finalBpm < 40 || $finalBpm > 300) {
file_put_contents($logFile, " - ⚠️ WARNING: Invalid BPM value ($finalBpm) - out of range 40-300, will randomize\n", FILE_APPEND | LOCK_EX);
$finalBpm = null; // Reset to null so it gets randomized
}
}
// Validate numerical key against actual API key
$finalNumericalKey = null;
if (isset($parsedMetadata['numerical_key']) && !empty($parsedMetadata['numerical_key'])) {
$promptNumericalKey = $parsedMetadata['numerical_key'];
$promptMusicalKey = $parsedMetadata['key'] ?? null;
// Normalize keys for comparison (case-insensitive, handle variations)
$normalizeKey = function($key) {
if (empty($key)) return '';
$key = strtolower(trim($key));
$key = str_replace(['maj', 'min'], ['major', 'minor'], $key);
$key = preg_replace('/\s+/', ' ', $key);
return ucwords($key);
};
$normalizedApiKey = $normalizeKey($finalKey);
$normalizedPromptKey = $normalizeKey($promptMusicalKey);
// Only use numerical key if the API's actual key matches what was in the prompt
if ($normalizedApiKey === $normalizedPromptKey) {
$finalNumericalKey = $promptNumericalKey;
file_put_contents($logFile, " - ✅ Numerical key validated: $promptNumericalKey matches API key $normalizedApiKey\n", FILE_APPEND | LOCK_EX);
} else {
file_put_contents($logFile, " - ⚠️ WARNING: Numerical key mismatch!\n", FILE_APPEND | LOCK_EX);
file_put_contents($logFile, " Prompt said: $promptNumericalKey – $normalizedPromptKey\n", FILE_APPEND | LOCK_EX);
file_put_contents($logFile, " API actually generated: $normalizedApiKey\n", FILE_APPEND | LOCK_EX);
file_put_contents($logFile, " → Numerical key NOT stored (using API's actual key as source of truth)\n", FILE_APPEND | LOCK_EX);
// Don't store numerical key if there's a mismatch - API's actual key is the truth
}
}
// If BPM still not set, DO NOT randomize - leave it as null
// API.box doesn't provide BPM in callbacks, so we use prompt BPM as fallback
if (!$finalBpm) {
file_put_contents($logFile, " - ⚠️ BPM NOT FOUND - leaving as NULL (will not display on card)\n", FILE_APPEND | LOCK_EX);
file_put_contents($logFile, " - BPM was not in API response and not found in prompt\n", FILE_APPEND | LOCK_EX);
} else {
file_put_contents($logFile, " - ✅ BPM found: $finalBpm (source: " . ($apiBpm ? 'API' : 'prompt') . ")\n", FILE_APPEND | LOCK_EX);
}
file_put_contents($logFile, " - FINAL VALUES: Genre=$finalGenre, BPM=" . ($finalBpm ?: 'NULL (not found)') . ", Key=$finalKey, Mood=$finalMood" . ($finalNumericalKey ? ", NumericalKey=$finalNumericalKey" : "") . "\n", FILE_APPEND | LOCK_EX);
file_put_contents($logFile, " - SOURCE: API=" . ($apiGenre || $apiBpm || $apiKey || $apiMood ? 'YES' : 'NO') . ", PARSED=" . (!empty($parsedMetadata) ? 'YES' : 'NO') . ", DEFAULTS=" . (!$apiGenre && !isset($parsedMetadata['genre']) ? 'YES' : 'NO') . "\n", FILE_APPEND | LOCK_EX);
return [
// Raw callback data for debugging
'raw_callback' => $data,
// Basic music information
// DEFAULT VALUES (used if API doesn't provide and prompt doesn't contain):
// - genre: 'Electronic'
// - bpm: random(80-160) if not found
// - key: 'C major'
// - mood: 'neutral'
// - time_signature: '4/4'
// - energy: 'medium'
// - instruments: ['synthesizer']
'genre' => $finalGenre,
'style' => $data['style'] ?? '',
'tags' => $data['tags'] ?? [],
'bpm' => $finalBpm, // API BPM if available, otherwise from prompt (API.box doesn't provide BPM in callbacks)
'key' => $finalKey,
'numerical_key' => $finalNumericalKey, // Only set if validated against API's actual key
'time_signature' => $data['time_signature'] ?? '4/4',
'mood' => $finalMood,
'energy' => $data['energy'] ?? 'medium',
'instruments' => $data['instruments'] ?? ['synthesizer'],
// Audio Quality Metrics
'audio_quality' => [
'bitrate' => $data['bitrate'] ?? $data['audio_bitrate'] ?? null,
'sample_rate' => $data['sample_rate'] ?? $data['audio_sample_rate'] ?? null,
'format' => $data['format'] ?? $data['audio_format'] ?? 'mp3',
'channels' => $data['channels'] ?? $data['audio_channels'] ?? 2,
'file_size' => $data['file_size'] ?? null,
'duration' => $data['duration'] ?? null,
'audio_quality_score' => $data['audio_quality_score'] ?? null
],
// Generation Parameters
'generation_parameters' => [
'model_version' => $data['model_version'] ?? $data['model'] ?? 'v3',
'model_name' => $data['model_name'] ?? null,
'temperature' => $data['temperature'] ?? null,
'top_p' => $data['top_p'] ?? null,
'max_tokens' => $data['max_tokens'] ?? null,
'seed' => $data['seed'] ?? null,
'parameters' => $data['parameters'] ?? $data['generation_params'] ?? []
],
// Processing Information
'processing_info' => [
'processing_time' => $data['processing_time'] ?? $data['generation_time'] ?? null,
'queue_time' => $data['queue_time'] ?? null,
'total_time' => $data['total_time'] ?? null,
'start_time' => $data['start_time'] ?? null,
'end_time' => $data['end_time'] ?? null,
'server_id' => $data['server_id'] ?? null,
'worker_id' => $data['worker_id'] ?? null
],
// Cost Information
'cost_info' => [
'api_cost' => $data['api_cost'] ?? $data['cost'] ?? null,
'credits_used' => $data['credits_used'] ?? null,
'currency' => $data['currency'] ?? 'USD',
'pricing_tier' => $data['pricing_tier'] ?? null,
'cost_per_second' => $data['cost_per_second'] ?? null
],
// Waveform Data
'waveform_data' => [
'waveform' => $data['waveform'] ?? $data['waveform_data'] ?? null,
'waveform_url' => $data['waveform_url'] ?? null,
'waveform_points' => $data['waveform_points'] ?? null,
'waveform_resolution' => $data['waveform_resolution'] ?? null
],
// Spectrum Analysis
'spectrum_analysis' => [
'spectrum' => $data['spectrum'] ?? $data['spectrum_data'] ?? null,
'spectrum_url' => $data['spectrum_url'] ?? null,
'frequency_data' => $data['frequency_data'] ?? null,
'spectral_centroid' => $data['spectral_centroid'] ?? null,
'spectral_rolloff' => $data['spectral_rolloff'] ?? null,
'spectral_bandwidth' => $data['spectral_bandwidth'] ?? null
],
// Audio Segments
'audio_segments' => [
'segments' => $data['segments'] ?? $data['audio_segments'] ?? [],
'verse_timestamps' => $data['verse_timestamps'] ?? null,
'chorus_timestamps' => $data['chorus_timestamps'] ?? null,
'bridge_timestamps' => $data['bridge_timestamps'] ?? null,
'intro_timestamps' => $data['intro_timestamps'] ?? null,
'outro_timestamps' => $data['outro_timestamps'] ?? null,
'section_labels' => $data['section_labels'] ?? null
],
// Error Details (for failed generations)
'error_details' => [
'error_code' => $data['error_code'] ?? $data['code'] ?? null,
'error_message' => $data['error_message'] ?? $data['msg'] ?? null,
'error_type' => $data['error_type'] ?? null,
'error_category' => $data['error_category'] ?? null,
'error_suggestions' => $data['error_suggestions'] ?? null,
'retry_available' => $data['retry_available'] ?? null,
'error_timestamp' => $data['error_timestamp'] ?? null
],
// Additional Analysis
'audio_analysis' => [
'loudness' => $data['loudness'] ?? null,
'dynamic_range' => $data['dynamic_range'] ?? null,
'peak_amplitude' => $data['peak_amplitude'] ?? null,
'rms_amplitude' => $data['rms_amplitude'] ?? null,
'zero_crossing_rate' => $data['zero_crossing_rate'] ?? null,
'harmonic_content' => $data['harmonic_content'] ?? null,
'percussive_content' => $data['percussive_content'] ?? null
],
// System Information
'system_info' => [
'created_with' => 'AI Music Generation',
'version' => '3.0',
'callback_processed' => date('Y-m-d H:i:s'),
'api_version' => $data['api_version'] ?? null,
'api_endpoint' => $data['api_endpoint'] ?? null
]
];
}
try {
// Parse the callback data
$data = json_decode($input, true);
if (!$data) {
throw new Exception('Invalid JSON data received');
}
// Log the parsed data
file_put_contents($logFile, "Parsed data: " . json_encode($data, JSON_PRETTY_PRINT) . "\n", FILE_APPEND | LOCK_EX);
// Handle different types of callbacks
if (isset($data['task_id'])) {
$taskId = $data['task_id'];
$status = $data['status'] ?? 'unknown';
// Fetch the original track to get the prompt and existing title
$originalData = getTrackOriginalData($taskId);
$originalPrompt = $originalData['prompt'];
$existingTitle = $originalData['title'];
file_put_contents($logFile, "📝 Original prompt: " . substr($originalPrompt ?: 'N/A', 0, 100) . "...\n", FILE_APPEND | LOCK_EX);
file_put_contents($logFile, "📝 Existing title: " . ($existingTitle ?: 'N/A') . "\n", FILE_APPEND | LOCK_EX);
// Extract comprehensive metadata (pass original prompt for parsing)
$enhanced_metadata = extractComprehensiveMetadata($data, $originalPrompt);
// Extract title from various possible locations in the API response
$apiTitle = extractTitleFromCallback($data);
if ($apiTitle) {
file_put_contents($logFile, "Found API title for task $taskId: $apiTitle\n", FILE_APPEND | LOCK_EX);
}
// Only use API title if track doesn't have a user-provided title
// Preserve user titles unless they're empty or default values
$title = null;
if ($existingTitle &&
$existingTitle !== '' &&
$existingTitle !== 'Untitled Track' &&
$existingTitle !== 'Generated Track') {
// Keep the user's title
$title = $existingTitle;
file_put_contents($logFile, "✅ Preserving user title: $title\n", FILE_APPEND | LOCK_EX);
} elseif ($apiTitle) {
// Use API title if no user title exists
$title = $apiTitle;
file_put_contents($logFile, "✅ Using API title: $title\n", FILE_APPEND | LOCK_EX);
} else {
// No title available
file_put_contents($logFile, "⚠️ No title available for task $taskId\n", FILE_APPEND | LOCK_EX);
}
// Extract duration from various possible locations in the API response
$duration = extractDurationFromCallback($data);
if ($duration) {
file_put_contents($logFile, "Found duration for task $taskId: $duration\n", FILE_APPEND | LOCK_EX);
}
// Extract tags from various possible locations in the API response
$tags = extractTagsFromCallback($data);
if ($tags) {
file_put_contents($logFile, "Found tags for task $taskId: " . (is_array($tags) ? implode(', ', $tags) : $tags) . "\n", FILE_APPEND | LOCK_EX);
}
// Extract model_name from various possible locations in the API response
$modelName = extractModelNameFromCallback($data);
if ($modelName) {
file_put_contents($logFile, "Found model_name for task $taskId: $modelName\n", FILE_APPEND | LOCK_EX);
}
// Extract lyrics from various possible locations in the API response
$lyrics = '';
// First, check if we have lyrics in the prompt field (this is where API.Box actually sends them)
if (isset($data['data']['data']) && is_array($data['data']['data'])) {
foreach ($data['data']['data'] as $item) {
if (isset($item['prompt']) && !empty($item['prompt'])) {
$lyrics = $item['prompt'];
file_put_contents($logFile, "Found lyrics in prompt field for task $taskId\n", FILE_APPEND | LOCK_EX);
break;
}
}
}
// Fallback to other possible locations
if (!$lyrics) {
if (isset($data['lyrics'])) {
$lyrics = $data['lyrics'];
} elseif (isset($data['lyric'])) {
$lyrics = $data['lyric'];
} elseif (isset($data['text'])) {
$lyrics = $data['text'];
} elseif (isset($data['data']['lyrics'])) {
$lyrics = $data['data']['lyrics'];
} elseif (isset($data['data']['lyric'])) {
$lyrics = $data['data']['lyric'];
} elseif (isset($data['data']['text'])) {
$lyrics = $data['data']['text'];
} elseif (isset($data['data']['data']['lyrics'])) {
$lyrics = $data['data']['data']['lyrics'];
} elseif (isset($data['data']['data']['lyric'])) {
$lyrics = $data['data']['data']['lyric'];
} elseif (isset($data['data']['data']['text'])) {
$lyrics = $data['data']['data']['text'];
} elseif (isset($data['data']['data']) && is_array($data['data']['data'])) {
// Handle nested array structure from API.Box
foreach ($data['data']['data'] as $item) {
if (isset($item['lyrics']) && !empty($item['lyrics'])) {
$lyrics = $item['lyrics'];
break;
} elseif (isset($item['lyric']) && !empty($item['lyric'])) {
$lyrics = $item['lyric'];
break;
} elseif (isset($item['text']) && !empty($item['text'])) {
$lyrics = $item['text'];
break;
}
}
}
}
// Clean up lyrics if found
if ($lyrics) {
// Remove common API formatting
$lyrics = str_replace(['[Verse]', '[Chorus]', '[Bridge]', '[Outro]', '[Intro]'], '', $lyrics);
$lyrics = preg_replace('/\[.*?\]/', '', $lyrics); // Remove any [bracketed] content
$lyrics = trim($lyrics);
// Log that we found lyrics
file_put_contents($logFile, "Found lyrics for task $taskId: " . substr($lyrics, 0, 100) . "...\n", FILE_APPEND | LOCK_EX);
} else {
// Debug: Log the data structure to see why lyrics weren't found
file_put_contents($logFile, "No lyrics found for task $taskId. Data structure:\n", FILE_APPEND | LOCK_EX);
file_put_contents($logFile, "Top level keys: " . implode(', ', array_keys($data)) . "\n", FILE_APPEND | LOCK_EX);
if (isset($data['data'])) {
file_put_contents($logFile, "Data level keys: " . implode(', ', array_keys($data['data'])) . "\n", FILE_APPEND | LOCK_EX);
if (isset($data['data']['data']) && is_array($data['data']['data'])) {
file_put_contents($logFile, "Data array has " . count($data['data']['data']) . " items\n", FILE_APPEND | LOCK_EX);
foreach ($data['data']['data'] as $index => $item) {
if (is_array($item)) {
file_put_contents($logFile, "Item $index keys: " . implode(', ', array_keys($item)) . "\n", FILE_APPEND | LOCK_EX);
}
}
}
}
file_put_contents($logFile, "No lyrics found for task $taskId\n", FILE_APPEND | LOCK_EX);
}
// Download and store audio files locally
$audioUrl = $data['audio_url'] ?? null;
$videoUrl = $data['video_url'] ?? null;
$localAudioUrl = null;
$localVideoUrl = null;
if ($audioUrl && $status === 'complete') {
$localAudioUrl = downloadAndStoreAudio($audioUrl, $taskId, 'main');
file_put_contents($logFile, "Downloaded main audio to: " . ($localAudioUrl ?: 'failed') . " for task $taskId\n", FILE_APPEND | LOCK_EX);
}
if ($videoUrl && $status === 'complete') {
$localVideoUrl = downloadAndStoreAudio($videoUrl, $taskId, 'video');
file_put_contents($logFile, "Downloaded video to: " . ($localVideoUrl ?: 'failed') . " for task $taskId\n", FILE_APPEND | LOCK_EX);
}
$metadata = json_encode($enhanced_metadata);
// Update the track with local URLs (fallback to external if download failed), including title, duration, tags, and model_name
updateMusicTrack($taskId, $status, $localAudioUrl ?: $audioUrl, $localVideoUrl ?: $videoUrl, $lyrics, $metadata, $duration, $title, $tags, $modelName);
// Store the task result for later retrieval (backup)
$resultFile = "task_results/{$taskId}.json";
// Ensure the directory exists
if (!is_dir('task_results')) {
mkdir('task_results', 0755, true);
}
// Save the result
file_put_contents($resultFile, json_encode($data, JSON_PRETTY_PRINT), LOCK_EX);
// Log the saved result
file_put_contents($logFile, "Task result saved to: $resultFile\n", FILE_APPEND | LOCK_EX);
// Return success response
echo json_encode([
'success' => true,
'message' => 'Callback processed successfully',
'task_id' => $taskId,
'status' => $status,
'local_audio_url' => $localAudioUrl,
'local_video_url' => $localVideoUrl
]);
} elseif (isset($data['id'])) {
// Alternative format with 'id' instead of 'task_id'
$taskId = $data['id'];
$status = $data['status'] ?? 'unknown';
// Fetch original prompt for metadata parsing
$originalData = getTrackOriginalData($taskId);
$originalPrompt = $originalData['prompt'];
$existingTitle = $originalData['title'];
// Extract comprehensive metadata (pass original prompt for parsing)
$enhanced_metadata = extractComprehensiveMetadata($data, $originalPrompt);
// Extract title from various possible locations in the API response
$apiTitle = extractTitleFromCallback($data);
if ($apiTitle) {
file_put_contents($logFile, "Found API title for task $taskId: $apiTitle\n", FILE_APPEND | LOCK_EX);
}
// Only use API title if track doesn't have a user-provided title
$title = null;
if ($existingTitle &&
$existingTitle !== '' &&
$existingTitle !== 'Untitled Track' &&
$existingTitle !== 'Generated Track') {
$title = $existingTitle;
file_put_contents($logFile, "✅ Preserving user title: $title\n", FILE_APPEND | LOCK_EX);
} elseif ($apiTitle) {
$title = $apiTitle;
file_put_contents($logFile, "✅ Using API title: $title\n", FILE_APPEND | LOCK_EX);
}
// Extract duration from various possible locations in the API response
$duration = extractDurationFromCallback($data);
if ($duration) {
file_put_contents($logFile, "Found duration for task $taskId: $duration\n", FILE_APPEND | LOCK_EX);
}
// Extract tags from various possible locations in the API response
$tags = extractTagsFromCallback($data);
if ($tags) {
file_put_contents($logFile, "Found tags for task $taskId: " . (is_array($tags) ? implode(', ', $tags) : $tags) . "\n", FILE_APPEND | LOCK_EX);
}
// Extract model_name from various possible locations in the API response
$modelName = extractModelNameFromCallback($data);
if ($modelName) {
file_put_contents($logFile, "Found model_name for task $taskId: $modelName\n", FILE_APPEND | LOCK_EX);
}
// Download and store audio files locally
$audioUrl = $data['audio_url'] ?? null;
$videoUrl = $data['video_url'] ?? null;
$localAudioUrl = null;
$localVideoUrl = null;
if ($audioUrl && $status === 'complete') {
$localAudioUrl = downloadAndStoreAudio($audioUrl, $taskId, 'main');
file_put_contents($logFile, "Downloaded main audio to: " . ($localAudioUrl ?: 'failed') . " for task $taskId\n", FILE_APPEND | LOCK_EX);
}
if ($videoUrl && $status === 'complete') {
$localVideoUrl = downloadAndStoreAudio($videoUrl, $taskId, 'video');
file_put_contents($logFile, "Downloaded video to: " . ($localVideoUrl ?: 'failed') . " for task $taskId\n", FILE_APPEND | LOCK_EX);
}
$metadata = json_encode($enhanced_metadata);
// Update the track with local URLs (fallback to external if download failed), including title, duration, tags, and model_name
updateMusicTrack($taskId, $status, $localAudioUrl ?: $audioUrl, $localVideoUrl ?: $videoUrl, null, $metadata, $duration, $title, $tags, $modelName);
// Store the task result for later retrieval (backup)
$resultFile = "task_results/{$taskId}.json";
// Ensure the directory exists
if (!is_dir('task_results')) {
mkdir('task_results', 0755, true);
}
// Save the result
file_put_contents($resultFile, json_encode($data, JSON_PRETTY_PRINT), LOCK_EX);
// Log the saved result
file_put_contents($logFile, "Task result saved to: $resultFile\n", FILE_APPEND | LOCK_EX);
// Return success response
echo json_encode([
'success' => true,
'message' => 'Callback processed successfully',
'task_id' => $taskId,
'status' => $status,
'local_audio_url' => $localAudioUrl,
'local_video_url' => $localVideoUrl
]);
} elseif (isset($data['code']) && $data['code'] == 531) {
// Handle API error 531 - generation failed
$taskId = $data['data']['task_id'] ?? 'unknown';
$errorMsg = $data['msg'] ?? 'Generation failed';
// Extract comprehensive error metadata
$enhanced_metadata = extractComprehensiveMetadata($data);
$metadata = json_encode($enhanced_metadata);
// Update database with failed status
updateMusicTrack($taskId, 'failed', null, null, null, $metadata);
// Log the error
file_put_contents($logFile, "API Error 531: $errorMsg for task $taskId\n", FILE_APPEND | LOCK_EX);
// Return success response (we handled the error)
echo json_encode([
'success' => true,
'message' => 'Error callback processed',
'task_id' => $taskId,
'status' => 'failed',
'error' => $errorMsg
]);
} elseif (isset($data['code']) && $data['code'] == 400) {
// Handle API error 400 - content violation (e.g., artist name restrictions)
$taskId = $data['data']['task_id'] ?? 'unknown';
$errorMsg = $data['msg'] ?? 'Content violation detected';
// Extract comprehensive error metadata
$enhanced_metadata = extractComprehensiveMetadata($data);
$metadata = json_encode($enhanced_metadata);
// Update database with failed status and store the specific error message
updateMusicTrack($taskId, 'failed', null, null, null, $metadata);
// Log the error
file_put_contents($logFile, "API Error 400: $errorMsg for task $taskId\n", FILE_APPEND | LOCK_EX);
// Return success response (we handled the error)
echo json_encode([
'success' => true,
'message' => 'Content violation callback processed',
'task_id' => $taskId,
'status' => 'failed',
'error' => $errorMsg,
'error_type' => 'content_violation'
]);
} elseif (isset($data['code']) && isset($data['data']['callbackType'])) {
// Handle API format with callbackType (both success and error cases)
$taskId = $data['data']['task_id'] ?? 'unknown';
$callbackType = $data['data']['callbackType'];
$audioData = $data['data']['data'] ?? [];
file_put_contents($logFile, "Processing API format with callbackType: code={$data['code']}, callbackType=$callbackType, taskId=$taskId\n", FILE_APPEND | LOCK_EX);
// Handle error cases (code 400, 531, etc.)
if ($data['code'] != 200) {
$errorMsg = $data['msg'] ?? 'API error occurred';
// Determine error type for better user feedback
$errorType = 'api_error';
if ($data['code'] == 400) {
$errorType = 'content_violation';
// Common content violations
if (stripos($errorMsg, 'artist name') !== false) {
$errorMsg = "Content violation: " . $errorMsg . " (Avoid mentioning existing artists, bands, or copyrighted material)";
} elseif (stripos($errorMsg, 'copyright') !== false) {
$errorMsg = "Content violation: " . $errorMsg . " (Avoid copyrighted material, song titles, or lyrics)";
} elseif (stripos($errorMsg, 'inappropriate') !== false) {
$errorMsg = "Content violation: " . $errorMsg . " (Avoid explicit or inappropriate content)";
}
} elseif ($data['code'] == 531) {
$errorType = 'generation_failed';
$errorMsg = "Generation failed: " . $errorMsg . " (Technical issue - try again or contact support)";
}
// Extract comprehensive error metadata
$enhanced_metadata = extractComprehensiveMetadata($data);
$metadata = json_encode($enhanced_metadata);
// Update database with failed status and enhanced error message
updateMusicTrack($taskId, 'failed', null, null, null, $metadata);
// Log the error with enhanced details
file_put_contents($logFile, "API Error {$data['code']} ($errorType): $errorMsg for task $taskId\n", FILE_APPEND | LOCK_EX);
// Return success response (we handled the error)
echo json_encode([
'success' => true,
'message' => 'Error callback processed',
'task_id' => $taskId,
'status' => 'failed',
'error' => $errorMsg,
'error_type' => $errorType
]);
return;
}
// Handle success case (code 200)
if ($callbackType === 'complete' && !empty($audioData)) {
// Sanitize all incoming data from DOM to prevent apostrophe issues
$audioData = sanitizeDOMArray($audioData);
$data = sanitizeDOMArray($data);
// Extract comprehensive metadata
$enhanced_metadata = extractComprehensiveMetadata($data);
// Extract title using helper function (checks multiple locations)
$title = extractTitleFromCallback($data);
if ($title) {
file_put_contents($logFile, "Found title for task $taskId: $title\n", FILE_APPEND | LOCK_EX);
}
// Extract duration using helper function
$duration = extractDurationFromCallback($data);
if ($duration) {
file_put_contents($logFile, "Found duration for task $taskId: $duration\n", FILE_APPEND | LOCK_EX);
}
// Extract tags using helper function
$tags = extractTagsFromCallback($data);
if ($tags) {
file_put_contents($logFile, "Found tags for task $taskId: " . (is_array($tags) ? implode(', ', $tags) : $tags) . "\n", FILE_APPEND | LOCK_EX);
}
// Extract model_name using helper function
$modelName = extractModelNameFromCallback($data);
if ($modelName) {
file_put_contents($logFile, "Found model_name for task $taskId: $modelName\n", FILE_APPEND | LOCK_EX);
}
// Get the first audio file with a valid URL for the main track
$audioUrl = null;
foreach ($audioData as $audio) {
// Try different possible audio URL fields
$audioUrl = $audio['audio_url'] ?? $audio['source_audio_url'] ?? $audio['stream_audio_url'] ?? null;
if (!empty($audioUrl)) {
// If title wasn't found by helper, try from audio item
if (!$title && isset($audio['title']) && !empty($audio['title'])) {
$title = sanitizeDOMText($audio['title']);
}
// If duration wasn't found by helper, try from audio item
if (!$duration && isset($audio['duration']) && !empty($audio['duration'])) {
$duration = floatval($audio['duration']);
}
// If tags weren't found by helper, try from audio item
if (!$tags && isset($audio['tags']) && !empty($audio['tags'])) {
$tags = is_array($audio['tags']) ? $audio['tags'] : [$audio['tags']];
$tags = sanitizeDOMArray($tags);
}
// If model_name wasn't found by helper, try from audio item
if (!$modelName && isset($audio['model_name']) && !empty($audio['model_name'])) {
$modelName = sanitizeDOMText($audio['model_name']);
}
file_put_contents($logFile, "Found audio URL: $audioUrl for task $taskId\n", FILE_APPEND | LOCK_EX);
break;
}
}
if ($audioUrl) {
// Download and store main audio file locally
$localAudioUrl = downloadAndStoreAudio($audioUrl, $taskId, 'main');
file_put_contents($logFile, "Downloaded main audio to: " . ($localAudioUrl ?: 'failed') . " for task $taskId\n", FILE_APPEND | LOCK_EX);
// Update database with complete status, local audio URL, duration, title, and metadata
$metadata = json_encode($enhanced_metadata);
file_put_contents($logFile, "🔍 About to call updateMusicTrack with: taskId=$taskId, status=complete, audioUrl=" . ($localAudioUrl ?: $audioUrl) . ", duration=$duration, title=" . ($title ?: 'none') . "\n", FILE_APPEND | LOCK_EX);
// Test database connection before update
$pdo = getDBConnection();
if ($pdo) {
file_put_contents($logFile, "🔍 Database connection test successful\n", FILE_APPEND | LOCK_EX);
} else {
file_put_contents($logFile, "❌ Database connection test failed\n", FILE_APPEND | LOCK_EX);
}
$updateResult = updateMusicTrack($taskId, 'complete', $localAudioUrl ?: $audioUrl, null, null, $metadata, $duration, $title, $tags, $modelName);
if ($updateResult) {
file_put_contents($logFile, "✅ Successfully updated track $taskId to complete with audio URL: " . ($localAudioUrl ?: $audioUrl) . ", duration: $duration\n", FILE_APPEND | LOCK_EX);
} else {
file_put_contents($logFile, "❌ FAILED to update track $taskId to complete in database!\n", FILE_APPEND | LOCK_EX);
file_put_contents($logFile, "🔍 Error details: updateMusicTrack returned false\n", FILE_APPEND | LOCK_EX);
// Try a direct database update as fallback
try {
$pdo = getDBConnection();
if ($pdo) {
$stmt = $pdo->prepare("UPDATE music_tracks SET status = 'complete', audio_url = ?, duration = ?, metadata = ?, updated_at = NOW() WHERE task_id = ?");
$directResult = $stmt->execute([$localAudioUrl ?: $audioUrl, $duration, $metadata, $taskId]);
if ($directResult) {
file_put_contents($logFile, "✅ Direct database update succeeded for task $taskId\n", FILE_APPEND | LOCK_EX);
} else {
$errorInfo = $stmt->errorInfo();
file_put_contents($logFile, "❌ Direct database update also failed: " . json_encode($errorInfo) . "\n", FILE_APPEND | LOCK_EX);
}
}
} catch (Exception $e) {
file_put_contents($logFile, "❌ Exception during direct database update: " . $e->getMessage() . "\n", FILE_APPEND | LOCK_EX);
}
}
// Store all variations in the audio_variations table
$pdo = getDBConnection();
if ($pdo) {
// Get the track ID
$stmt = $pdo->prepare("SELECT id FROM music_tracks WHERE task_id = ?");
$stmt->execute([$taskId]);
$track = $stmt->fetch();
if ($track) {
$trackId = $track['id'];
// Clear existing variations for this track
$stmt = $pdo->prepare("DELETE FROM audio_variations WHERE track_id = ?");
$stmt->execute([$trackId]);
// Insert each variation
foreach ($audioData as $index => $variation) {
if (isset($variation['audio_url']) && !empty($variation['audio_url'])) {
// Download and store variation audio file locally
$localVariationUrl = downloadAndStoreAudio($variation['audio_url'], $taskId, 'variation', $index);
// Extract variation-specific metadata
// Generate a meaningful title for the variation if none provided
$variationTitle = $variation['title'] ?? null;
if (!$variationTitle) {
// Create a descriptive title based on available metadata
$titleParts = [];
if (isset($variation['genre'])) $titleParts[] = $variation['genre'];
if (isset($variation['style'])) $titleParts[] = $variation['style'];
if (isset($variation['mood'])) $titleParts[] = $variation['mood'];
if (isset($variation['energy'])) $titleParts[] = $variation['energy'];
if (!empty($titleParts)) {
$variationTitle = implode(' ', $titleParts) . ' Variation';
} else {
$variationTitle = "AI Variation " . ($index + 1);
}
}
$variationMetadata = [
'genre' => $variation['genre'] ?? $variation['tags'][0] ?? null,
'style' => $variation['style'] ?? null,
'bpm' => $variation['bpm'] ?? $variation['tempo'] ?? null,
'key' => $variation['key'] ?? null,
'mood' => $variation['mood'] ?? null,
'energy' => $variation['energy'] ?? null,
'instruments' => $variation['instruments'] ?? null,
'tags' => $variation['tags'] ?? null,
'duration' => $variation['duration'] ?? null,
'title' => $variationTitle
];
$stmt = $pdo->prepare("
INSERT INTO audio_variations
(track_id, variation_index, audio_url, duration, title, tags, image_url, source_audio_url, stream_audio_url, metadata)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
");
$stmt->execute([
$trackId,
$index,
$localVariationUrl ?: $variation['audio_url'],
$variation['duration'] ?? null,
$variationTitle,
is_array($variation['tags']) ? implode(', ', $variation['tags']) : $variation['tags'],
$variation['image_url'] ?? null,
$variation['source_audio_url'] ?? null,
$variation['stream_audio_url'] ?? null,
json_encode($variationMetadata)
]);
file_put_contents($logFile, "Stored variation $index for track $taskId with metadata: " . json_encode($variationMetadata) . "\n", FILE_APPEND | LOCK_EX);
}
}
// Update the main track with variations count and default selection
$variations_count = count($audioData);
$stmt = $pdo->prepare("UPDATE music_tracks SET variations_count = ?, selected_variation = 0 WHERE id = ?");
$stmt->execute([$variations_count, $trackId]);
file_put_contents($logFile, "Stored $variations_count variations for track $taskId\n", FILE_APPEND | LOCK_EX);
}
}
// Return success response for complete callback
echo json_encode([
'success' => true,
'message' => 'Complete callback processed successfully',
'task_id' => $taskId,
'status' => 'complete',
'local_audio_url' => $localAudioUrl ?: $audioUrl,
'duration' => $duration
]);
return;
} else {
// No valid audio URL found
$enhanced_metadata = extractComprehensiveMetadata($data);
$metadata = json_encode($enhanced_metadata);
updateMusicTrack($taskId, 'failed', null, null, null, $metadata);
file_put_contents($logFile, "No valid audio URL found for task $taskId\n", FILE_APPEND | LOCK_EX);
}
} else {
// Other callback types (text, first, etc.) - extract all available fields and update metadata
$enhanced_metadata = extractComprehensiveMetadata($data);
$metadata = json_encode($enhanced_metadata);
// Extract title from various possible locations in the API response
$title = extractTitleFromCallback($data);
if ($title) {
file_put_contents($logFile, "Found title for task $taskId (callbackType: $callbackType): $title\n", FILE_APPEND | LOCK_EX);
}
// Extract duration from various possible locations in the API response
$duration = extractDurationFromCallback($data);
if ($duration) {
file_put_contents($logFile, "Found duration for task $taskId (callbackType: $callbackType): $duration\n", FILE_APPEND | LOCK_EX);
}
// Extract tags from various possible locations in the API response
$tags = extractTagsFromCallback($data);
if ($tags) {
file_put_contents($logFile, "Found tags for task $taskId (callbackType: $callbackType): " . (is_array($tags) ? implode(', ', $tags) : $tags) . "\n", FILE_APPEND | LOCK_EX);
}
// Extract model_name from various possible locations in the API response
$modelName = extractModelNameFromCallback($data);
if ($modelName) {
file_put_contents($logFile, "Found model_name for task $taskId (callbackType: $callbackType): $modelName\n", FILE_APPEND | LOCK_EX);
}
updateMusicTrack($taskId, 'processing', null, null, null, $metadata, $duration, $title, $tags, $modelName);
file_put_contents($logFile, "Updated track $taskId to processing (callbackType: $callbackType)\n", FILE_APPEND | LOCK_EX);
}
// Store the task result for later retrieval (backup)
$resultFile = "task_results/{$taskId}.json";
// Ensure the directory exists
if (!is_dir('task_results')) {
mkdir('task_results', 0755, true);
}
// Save the result
file_put_contents($resultFile, json_encode($data, JSON_PRETTY_PRINT), LOCK_EX);
// Return success response
echo json_encode([
'success' => true,
'message' => 'Callback processed successfully',
'task_id' => $taskId,
'status' => $callbackType === 'complete' ? 'complete' : 'processing'
]);
} else {
// Unknown callback format
file_put_contents($logFile, "Unknown callback format\n", FILE_APPEND | LOCK_EX);
echo json_encode([
'success' => true,
'message' => 'Callback received (unknown format)',
'data' => $data
]);
}
} catch (Exception $e) {
// Log the error
file_put_contents($logFile, "Error: " . $e->getMessage() . "\n", FILE_APPEND | LOCK_EX);
http_response_code(500);
echo json_encode([
'success' => false,
'error' => $e->getMessage()
]);
}
?>