T.ME/BIBIL_0DAY
CasperSecurity


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/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : /home/gositeme/domains/soundstudiopro.com/public_html/callback.php.backup.20251111_174613
<?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(
        ['\'', '"', '&apos;', '&quot;', '&#39;', '&#34;', '&rsquo;', '&ldquo;', '&rdquo;'],
        ['\'', '"', '\'', '"', '\'', '"', '\'', '"', '"'],
        $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()
    ]);
}
?> 

CasperSecurity Mini