![]() 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/private_html/ |
<?php
/**
* Fix Stuck Processing Tracks
*
* Finds tracks that are "processing" in the database but "complete" on the API
* and processes them like a callback would (downloads audio, extracts metadata, etc.)
*/
error_reporting(E_ALL);
ini_set('display_errors', 1);
require_once 'config/database.php';
// Include callback functions - define constant to allow inclusion
define('CALLBACK_INCLUDED', true);
require_once 'callback.php';
$api_key = '63edba40620216c5aa2c04240ac41dbd';
$logFile = 'fix_stuck_tracks_log.txt';
echo "<h1>đ§ Fix Stuck Processing Tracks</h1>";
echo "<p>This script finds tracks stuck in 'processing' status and processes them</p>";
$pdo = getDBConnection();
if (!$pdo) {
die("<p style='color: red;'>â Database connection failed</p>");
}
// Get all tracks with status='processing' that have a valid task_id
// Also check for tracks that might have been stuck for a while
$stmt = $pdo->prepare("
SELECT id, task_id, title, user_id, prompt, created_at, status
FROM music_tracks
WHERE status = 'processing'
AND task_id IS NOT NULL
AND task_id != ''
AND task_id != 'unknown'
AND task_id NOT LIKE 'temp_%'
AND task_id NOT LIKE 'retry_%'
ORDER BY created_at DESC
LIMIT 200
");
$stmt->execute();
$stuckTracks = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Also check for tracks that are processing for more than 10 minutes (likely stuck)
$stmtOld = $pdo->prepare("
SELECT id, task_id, title, user_id, prompt, created_at, status
FROM music_tracks
WHERE status = 'processing'
AND task_id IS NOT NULL
AND task_id != ''
AND task_id != 'unknown'
AND task_id NOT LIKE 'temp_%'
AND task_id NOT LIKE 'retry_%'
AND created_at < DATE_SUB(NOW(), INTERVAL 10 MINUTE)
ORDER BY created_at DESC
LIMIT 200
");
$stmtOld->execute();
$oldStuckTracks = $stmtOld->fetchAll(PDO::FETCH_ASSOC);
// Merge and deduplicate by id
$allStuckTracks = [];
$seenIds = [];
foreach (array_merge($stuckTracks, $oldStuckTracks) as $track) {
if (!in_array($track['id'], $seenIds)) {
$allStuckTracks[] = $track;
$seenIds[] = $track['id'];
}
}
$stuckTracks = $allStuckTracks;
// Also check for tracks that are complete but missing variations
$stmtMissingVars = $pdo->prepare("
SELECT mt.id, mt.task_id, mt.title, mt.user_id, mt.created_at, mt.variations_count,
(SELECT COUNT(*) FROM audio_variations WHERE track_id = mt.id) as actual_variations_count
FROM music_tracks mt
WHERE mt.status = 'complete'
AND mt.task_id IS NOT NULL
AND mt.task_id != ''
AND mt.task_id != 'unknown'
AND mt.task_id NOT LIKE 'temp_%'
AND mt.task_id NOT LIKE 'retry_%'
AND (
(SELECT COUNT(*) FROM audio_variations WHERE track_id = mt.id) = 0
OR mt.variations_count = 0
)
ORDER BY mt.created_at DESC
LIMIT 200
");
$stmtMissingVars->execute();
$tracksMissingVariations = $stmtMissingVars->fetchAll(PDO::FETCH_ASSOC);
if (empty($stuckTracks) && empty($tracksMissingVariations)) {
echo "<p style='color: green;'>â
No stuck tracks found!</p>";
echo "<p>Checked for tracks with status='processing' that have valid task_ids.</p>";
// Show some stats
$statsStmt = $pdo->query("
SELECT
COUNT(*) as total_processing,
COUNT(CASE WHEN created_at < DATE_SUB(NOW(), INTERVAL 10 MINUTE) THEN 1 END) as old_processing
FROM music_tracks
WHERE status = 'processing'
");
$stats = $statsStmt->fetch(PDO::FETCH_ASSOC);
echo "<p><strong>Stats:</strong></p>";
echo "<ul>";
echo "<li>Total processing tracks: " . $stats['total_processing'] . "</li>";
echo "<li>Processing > 10 minutes: " . $stats['old_processing'] . "</li>";
echo "</ul>";
exit;
}
// If we have tracks missing variations, add them to process
if (!empty($tracksMissingVariations)) {
echo "<h2>đĩ Tracks Missing Variations</h2>";
echo "<p>Found " . count($tracksMissingVariations) . " complete track(s) that may be missing variations</p>";
echo "<hr>";
// Merge with stuck tracks
$allTracksToProcess = array_merge($stuckTracks, $tracksMissingVariations);
$stuckTracks = $allTracksToProcess;
}
echo "<p>Found " . count($stuckTracks) . " track(s) to check</p>";
if (!empty($tracksMissingVariations)) {
echo "<p style='color: orange;'>â ī¸ " . count($tracksMissingVariations) . " of these are complete but may be missing variations</p>";
}
echo "<hr>";
$fixedCount = 0;
$stillProcessingCount = 0;
$failedCount = 0;
$errorCount = 0;
foreach ($stuckTracks as $track) {
$trackId = $track['id'];
$taskId = $track['task_id'];
$title = $track['title'];
$currentStatus = $track['status'] ?? 'processing';
$hasVariations = isset($track['actual_variations_count']) ? ($track['actual_variations_count'] > 0) : false;
$variationsCount = $track['variations_count'] ?? 0;
echo "<div style='border: 1px solid #ccc; padding: 15px; margin: 10px 0;'>";
echo "<h3>Track #{$trackId}: " . htmlspecialchars($title) . "</h3>";
echo "<p><strong>Task ID:</strong> {$taskId}</p>";
echo "<p><strong>Status:</strong> {$currentStatus}</p>";
echo "<p><strong>Created:</strong> {$track['created_at']}</p>";
// If track is complete but missing variations, check task_results first
if ($currentStatus === 'complete' && !$hasVariations) {
echo "<p style='color: orange;'>â ī¸ Track is complete but has no variations - checking task_results...</p>";
$taskResultFile = "task_results/{$taskId}.json";
if (file_exists($taskResultFile)) {
echo "<p style='color: green;'>â
Found task_results JSON file</p>";
$taskResultContent = file_get_contents($taskResultFile);
$taskResultData = json_decode($taskResultContent, true);
if ($taskResultData && isset($taskResultData['data']['data']) && is_array($taskResultData['data']['data']) && count($taskResultData['data']['data']) > 1) {
echo "<p>Found " . count($taskResultData['data']['data']) . " items in task_results - processing variations...</p>";
// Process variations (same code as 404 case)
$verifyStmt = $pdo->prepare("SELECT id, task_id FROM music_tracks WHERE task_id = ? OR id = ?");
$verifyStmt->execute([$taskId, $trackId]);
$verifyTrack = $verifyStmt->fetch(PDO::FETCH_ASSOC);
if ($verifyTrack) {
$trackIdForVariations = $verifyTrack['id'];
$actualTaskId = $verifyTrack['task_id'] ?: $taskId;
// Clear existing variations
$clearStmt = $pdo->prepare("DELETE FROM audio_variations WHERE track_id = ?");
$clearStmt->execute([$trackIdForVariations]);
$storedVariationsCount = 0;
$variationIndex = 0;
foreach ($taskResultData['data']['data'] as $originalIndex => $variation) {
$variationAudioUrl = (!empty($variation['audio_url'])) ? $variation['audio_url'] :
((!empty($variation['source_audio_url'])) ? $variation['source_audio_url'] :
((!empty($variation['stream_audio_url'])) ? $variation['stream_audio_url'] : null));
if (!empty($variationAudioUrl)) {
$localVariationUrl = downloadAndStoreAudio($variationAudioUrl, $actualTaskId, 'variation', $variationIndex);
$variationTitle = $variation['title'] ?? "AI Variation " . ($variationIndex + 1);
$variationMetadata = [
'genre' => $variation['genre'] ?? null,
'style' => $variation['style'] ?? null,
'bpm' => $variation['bpm'] ?? $variation['tempo'] ?? null,
'key' => $variation['key'] ?? null,
'mood' => $variation['mood'] ?? null,
'energy' => $variation['energy'] ?? null,
'tags' => $variation['tags'] ?? null,
'duration' => $variation['duration'] ?? null,
'title' => $variationTitle
];
$variationImageUrl = null;
if (isset($variation['image_url']) && !empty($variation['image_url'])) {
$variationImageUrl = downloadAndStoreImage($variation['image_url'], $actualTaskId . '_var' . $variationIndex);
}
try {
$variationStmt = $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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
");
$variationTags = is_array($variation['tags']) ? implode(', ', $variation['tags']) : ($variation['tags'] ?? '');
$insertResult = $variationStmt->execute([
$trackIdForVariations,
$variationIndex,
$localVariationUrl ?: $variationAudioUrl,
$variation['duration'] ?? null,
$variationTitle,
$variationTags ?: null,
$variationImageUrl,
$variation['source_audio_url'] ?? null,
$variation['stream_audio_url'] ?? null,
json_encode($variationMetadata)
]);
if ($insertResult) {
$storedVariationsCount++;
$variationIndex++;
echo "<p style='color: green;'>â
Stored variation {$variationIndex}: {$variationTitle}</p>";
} else {
$errorInfo = $variationStmt->errorInfo();
echo "<p style='color: red;'>â Failed to insert variation: " . htmlspecialchars(json_encode($errorInfo)) . "</p>";
}
} catch (Exception $e) {
echo "<p style='color: red;'>â Exception: " . htmlspecialchars($e->getMessage()) . "</p>";
}
}
}
if ($storedVariationsCount > 0) {
$updateVarCountStmt = $pdo->prepare("UPDATE music_tracks SET variations_count = ?, selected_variation = 0 WHERE id = ?");
$updateVarCountStmt->execute([$storedVariationsCount, $trackIdForVariations]);
echo "<p style='color: green; font-weight: bold;'>â
Stored {$storedVariationsCount} variation(s) for this track!</p>";
$fixedCount++;
}
}
echo "</div>";
continue; // Skip API check for this track since we just processed variations
} else {
echo "<p style='color: orange;'>â ī¸ task_results has " . (isset($taskResultData['data']['data']) ? count($taskResultData['data']['data']) : 0) . " items (need > 1 for variations)</p>";
}
} else {
echo "<p style='color: orange;'>â ī¸ No task_results JSON file found</p>";
}
}
// Check API status
$api_url = "https://api.api.box/api/v1/status/{$taskId}";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $api_url);
curl_setopt($ch, CURLOPT_HTTPGET, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $api_key,
'Content-Type: application/json',
'User-Agent: SoundStudioPro-Fix/1.0'
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curl_error = curl_error($ch);
curl_close($ch);
if ($curl_error) {
echo "<p style='color: red;'>â cURL Error: {$curl_error}</p>";
$errorCount++;
echo "</div>";
continue;
}
if ($http_code !== 200) {
echo "<p style='color: orange;'>â ī¸ API returned HTTP {$http_code}</p>";
if ($http_code === 404) {
echo "<p>Track data not available</p>";
echo "<p>đ Checking for local files or saved task results...</p>";
// Check if track already has an audio_url in database
$stmt_check = $pdo->prepare("SELECT audio_url FROM music_tracks WHERE id = ?");
$stmt_check->execute([$trackId]);
$existingAudioUrl = $stmt_check->fetchColumn();
// Check for local audio file
$audioExtensions = ['mp3', 'wav', 'm4a', 'ogg'];
$localAudioUrl = null;
foreach ($audioExtensions as $ext) {
$localPath = "audio_files/{$taskId}.{$ext}";
if (file_exists($localPath)) {
$localAudioUrl = "/audio_files/{$taskId}.{$ext}";
echo "<p style='color: green;'>â
Found local audio file: {$localAudioUrl}</p>";
break;
}
}
// If track has audio_url but no local file, try to download it
if (!$localAudioUrl && $existingAudioUrl && filter_var($existingAudioUrl, FILTER_VALIDATE_URL)) {
echo "<p>đĨ Track has external audio URL, attempting to download...</p>";
$localAudioUrl = downloadAndStoreAudio($existingAudioUrl, $taskId, 'main');
if ($localAudioUrl) {
echo "<p style='color: green;'>â
Downloaded audio from existing URL</p>";
} else {
echo "<p style='color: orange;'>â ī¸ Failed to download, will use existing URL</p>";
$localAudioUrl = $existingAudioUrl; // Use existing URL as fallback
}
}
// Check for task_results JSON file
$taskResultFile = "task_results/{$taskId}.json";
$taskResultData = null;
if (file_exists($taskResultFile)) {
echo "<p style='color: green;'>â
Found task_results JSON file</p>";
$taskResultContent = file_get_contents($taskResultFile);
$taskResultData = json_decode($taskResultContent, true);
if ($taskResultData) {
// Extract audio URL from task results if we don't have local file
if (!$localAudioUrl) {
if (isset($taskResultData['data']['data']) && is_array($taskResultData['data']['data'])) {
foreach ($taskResultData['data']['data'] as $audio) {
$audioUrl = (!empty($audio['audio_url'])) ? $audio['audio_url'] :
((!empty($audio['source_audio_url'])) ? $audio['source_audio_url'] :
((!empty($audio['stream_audio_url'])) ? $audio['stream_audio_url'] : null));
if ($audioUrl) {
// Try to download it now
$localAudioUrl = downloadAndStoreAudio($audioUrl, $taskId, 'main');
if ($localAudioUrl) {
echo "<p style='color: green;'>â
Downloaded audio from task_results data</p>";
}
break;
}
}
}
}
}
}
// If we have local audio or task results, mark as complete
if ($localAudioUrl || $taskResultData) {
echo "<p style='color: green;'>â
Recovering track from local files/task results...</p>";
// Get original track data
$originalData = getTrackOriginalData($taskId);
$originalPrompt = $originalData['prompt'] ?? $track['prompt'];
// Process task_results JSON to extract ALL data
$title = $track['title'];
$duration = null;
$tags = null;
$modelName = null;
$lyrics = '';
$imageUrl = null;
$localImageUrl = null;
// Extract data from task_results JSON
if ($taskResultData && isset($taskResultData['data']['data']) && is_array($taskResultData['data']['data'])) {
// Find the first item with audio_url (the complete one)
$completeItem = null;
foreach ($taskResultData['data']['data'] as $item) {
$itemAudioUrl = (!empty($item['audio_url'])) ? $item['audio_url'] :
((!empty($item['source_audio_url'])) ? $item['source_audio_url'] :
((!empty($item['stream_audio_url'])) ? $item['stream_audio_url'] : null));
if ($itemAudioUrl) {
$completeItem = $item;
break;
}
}
// If no complete item, use first item
if (!$completeItem && !empty($taskResultData['data']['data'])) {
$completeItem = $taskResultData['data']['data'][0];
}
if ($completeItem) {
// Extract title
if (isset($completeItem['title']) && !empty($completeItem['title'])) {
$title = $title ?: $completeItem['title'];
}
// Extract duration
if (isset($completeItem['duration']) && !empty($completeItem['duration'])) {
$duration = floatval($completeItem['duration']);
}
// Extract tags
if (isset($completeItem['tags']) && !empty($completeItem['tags'])) {
$tags = is_array($completeItem['tags']) ? $completeItem['tags'] : explode(',', $completeItem['tags']);
$tags = array_map('trim', $tags);
$tags = formatTagsForStorage($tags);
}
// Extract model_name
if (isset($completeItem['model_name']) && !empty($completeItem['model_name'])) {
$modelName = $completeItem['model_name'];
}
// Extract lyrics (from prompt field)
if (isset($completeItem['prompt']) && !empty($completeItem['prompt'])) {
$lyrics = $completeItem['prompt'];
}
// Extract image URL
if (isset($completeItem['image_url']) && !empty($completeItem['image_url'])) {
$imageUrl = $completeItem['image_url'];
} elseif (isset($completeItem['source_image_url']) && !empty($completeItem['source_image_url'])) {
$imageUrl = $completeItem['source_image_url'];
}
}
// Also check all items for lyrics if not found
if (!$lyrics) {
foreach ($taskResultData['data']['data'] as $item) {
if (isset($item['prompt']) && !empty($item['prompt']) && $item['prompt'] !== '[Instrumental]') {
$lyrics = $item['prompt'];
break;
}
}
}
}
// Clean up lyrics
if ($lyrics && $lyrics !== '[Instrumental]') {
$lyrics = str_replace(['[Verse]', '[Chorus]', '[Bridge]', '[Outro]', '[Intro]', '[Instrumental]'], '', $lyrics);
$lyrics = preg_replace('/\[.*?\]/', '', $lyrics);
$lyrics = trim($lyrics);
if (empty($lyrics)) {
$lyrics = null;
}
} else {
$lyrics = null;
}
// Download image if we have URL
if ($imageUrl) {
$localImageUrl = downloadAndStoreImage($imageUrl, $taskId);
if ($localImageUrl) {
echo "<p style='color: green;'>â
Downloaded image: {$localImageUrl}</p>";
}
}
// If we still don't have audio URL but have task results, try to get it
if (!$localAudioUrl && $taskResultData && isset($taskResultData['data']['data'])) {
foreach ($taskResultData['data']['data'] as $item) {
$itemAudioUrl = (!empty($item['audio_url'])) ? $item['audio_url'] :
((!empty($item['source_audio_url'])) ? $item['source_audio_url'] :
((!empty($item['stream_audio_url'])) ? $item['stream_audio_url'] : null));
if ($itemAudioUrl) {
$localAudioUrl = downloadAndStoreAudio($itemAudioUrl, $taskId, 'main');
if ($localAudioUrl) {
echo "<p style='color: green;'>â
Downloaded audio from task_results: {$localAudioUrl}</p>";
break;
}
}
}
}
// Build callback data structure for metadata extraction
$callbackData = $taskResultData ?: [
'task_id' => $taskId,
'status' => 'complete',
'data' => []
];
// Extract comprehensive metadata
$enhanced_metadata = extractComprehensiveMetadata($callbackData, $originalPrompt);
// Override with extracted values
if ($tags) {
$enhanced_metadata['tags'] = $tags;
}
if ($duration) {
$enhanced_metadata['audio_quality']['duration'] = $duration;
}
if ($modelName) {
$enhanced_metadata['generation_parameters']['model_name'] = $modelName;
}
// Store tags in metadata
if ($tags) {
$enhanced_metadata['tags'] = $tags;
}
// Display extracted data
echo "<p><strong>Extracted Data:</strong></p>";
echo "<ul>";
echo "<li>Title: " . htmlspecialchars($title ?: 'N/A') . "</li>";
echo "<li>Duration: " . ($duration ? number_format($duration, 2) . "s" : 'N/A') . "</li>";
echo "<li>Tags: " . htmlspecialchars($tags ?: 'N/A') . "</li>";
echo "<li>Model: " . htmlspecialchars($modelName ?: 'N/A') . "</li>";
echo "<li>Lyrics: " . ($lyrics ? "Yes (" . strlen($lyrics) . " chars)" : 'No') . "</li>";
echo "<li>Audio: " . ($localAudioUrl ? htmlspecialchars($localAudioUrl) : 'N/A') . "</li>";
echo "<li>Image: " . ($localImageUrl ? htmlspecialchars($localImageUrl) : 'N/A') . "</li>";
echo "</ul>";
$metadata = json_encode($enhanced_metadata);
// Verify track exists with this task_id
$verifyStmt = $pdo->prepare("SELECT id, task_id FROM music_tracks WHERE task_id = ? OR id = ?");
$verifyStmt->execute([$taskId, $trackId]);
$verifyTrack = $verifyStmt->fetch(PDO::FETCH_ASSOC);
if (!$verifyTrack) {
echo "<p style='color: red;'>â Track not found in database with task_id {$taskId} or id {$trackId}</p>";
$errorCount++;
echo "</div>";
continue;
}
// Use the actual task_id from database if different
$actualTaskId = $verifyTrack['task_id'] ?: $taskId;
if ($actualTaskId !== $taskId) {
echo "<p style='color: orange;'>â ī¸ Task ID mismatch: DB has '{$actualTaskId}', using that instead</p>";
}
// CRITICAL: Check if there are multiple tracks with the same task_id
// If so, we need to consolidate them - one main track, others as variations
$duplicateTracksStmt = $pdo->prepare("
SELECT id, title, audio_url, image_url, duration, created_at
FROM music_tracks
WHERE task_id = ? AND status = 'complete'
ORDER BY created_at ASC, id ASC
");
$duplicateTracksStmt->execute([$actualTaskId]);
$duplicateTracks = $duplicateTracksStmt->fetchAll(PDO::FETCH_ASSOC);
$storedVariationsCount = 0; // Initialize for duplicate consolidation
$trackIdForVariations = $verifyTrack['id']; // Default to current track
if (count($duplicateTracks) > 1) {
echo "<p style='color: orange;'>â ī¸ Found " . count($duplicateTracks) . " tracks with same task_id - consolidating into variations...</p>";
// First track is the main one
$mainTrackId = $duplicateTracks[0]['id'];
$otherTracks = array_slice($duplicateTracks, 1);
echo "<p>Main track: #{$mainTrackId} ({$duplicateTracks[0]['title']})</p>";
echo "<p>Converting " . count($otherTracks) . " duplicate track(s) to variations...</p>";
// Get current variation count for main track
$currentVarCountStmt = $pdo->prepare("SELECT COUNT(*) FROM audio_variations WHERE track_id = ?");
$currentVarCountStmt->execute([$mainTrackId]);
$storedVariationsCount = $currentVarCountStmt->fetchColumn();
// Convert other tracks to variations
foreach ($otherTracks as $otherTrack) {
$variationIndex = $storedVariationsCount; // Use current count
// Check if this track already exists as a variation
$checkVarStmt = $pdo->prepare("SELECT COUNT(*) FROM audio_variations WHERE track_id = ? AND audio_url = ?");
$checkVarStmt->execute([$mainTrackId, $otherTrack['audio_url']]);
$exists = $checkVarStmt->fetchColumn() > 0;
if (!$exists && !empty($otherTrack['audio_url'])) {
try {
$convertStmt = $pdo->prepare("
INSERT INTO audio_variations
(track_id, variation_index, audio_url, duration, title, tags, image_url, metadata)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
");
$convertMetadata = json_encode([
'title' => $otherTrack['title'],
'duration' => $otherTrack['duration'],
'converted_from_track_id' => $otherTrack['id'],
'converted_at' => date('Y-m-d H:i:s')
]);
$convertResult = $convertStmt->execute([
$mainTrackId,
$variationIndex,
$otherTrack['audio_url'],
$otherTrack['duration'],
$otherTrack['title'] ?: "Variation " . ($variationIndex + 1),
null,
$otherTrack['image_url'],
$convertMetadata
]);
if ($convertResult) {
$storedVariationsCount++;
echo "<p style='color: green;'>â
Converted track #{$otherTrack['id']} ({$otherTrack['title']}) to variation #{$variationIndex}</p>";
// Optionally delete the duplicate track (or mark as deleted)
// $deleteStmt = $pdo->prepare("DELETE FROM music_tracks WHERE id = ?");
// $deleteStmt->execute([$otherTrack['id']]);
}
} catch (Exception $e) {
echo "<p style='color: red;'>â Failed to convert track #{$otherTrack['id']}: " . htmlspecialchars($e->getMessage()) . "</p>";
}
}
}
// Use main track ID for variations
$trackIdForVariations = $mainTrackId;
echo "<p style='color: green;'>â
All variations will be linked to main track #{$mainTrackId}</p>";
} else {
$trackIdForVariations = $verifyTrack['id'];
}
// Update track - try direct SQL update if updateMusicTrack fails
$updateResult = updateMusicTrack(
$actualTaskId,
'complete',
$localAudioUrl,
null,
!empty($lyrics) ? $lyrics : null,
$metadata,
$duration,
$title,
$tags,
$modelName,
$localImageUrl
);
if ($updateResult) {
echo "<p style='color: green; font-weight: bold;'>â
Successfully recovered and marked as complete!</p>";
// Process variations from task_results
if ($taskResultData && isset($taskResultData['data']['data']) && is_array($taskResultData['data']['data']) && count($taskResultData['data']['data']) > 1) {
echo "<p>đĩ Processing variations from task_results...</p>";
// Get the track ID (not task_id) - CRITICAL: This is the MAIN track that variations will be linked to
// Use $trackIdForVariations if set from duplicate consolidation, otherwise use verifyTrack id
if (!isset($trackIdForVariations)) {
$trackIdForVariations = $verifyTrack['id'];
}
echo "<p><strong>đ Linking variations to Main Track ID:</strong> {$trackIdForVariations}</p>";
echo "<p><strong>Task ID:</strong> {$actualTaskId}</p>";
// Clear existing variations for this track
$clearStmt = $pdo->prepare("DELETE FROM audio_variations WHERE track_id = ?");
$clearStmt->execute([$trackIdForVariations]);
$deletedCount = $clearStmt->rowCount();
echo "<p>Cleared {$deletedCount} existing variation(s) for track #{$trackIdForVariations}</p>";
$storedVariationsCount = 0;
$variationIndex = 0;
echo "<p>Found " . count($taskResultData['data']['data']) . " item(s) in task_results data array - processing ALL as variations...</p>";
foreach ($taskResultData['data']['data'] as $originalIndex => $variation) {
// Check for audio URL (same logic as callback)
$variationAudioUrl = (!empty($variation['audio_url'])) ? $variation['audio_url'] :
((!empty($variation['source_audio_url'])) ? $variation['source_audio_url'] :
((!empty($variation['stream_audio_url'])) ? $variation['stream_audio_url'] : null));
echo "<p>Processing item {$originalIndex}: audio_url=" . ($variation['audio_url'] ?? 'empty') . ", source=" . ($variation['source_audio_url'] ?? 'none') . ", stream=" . ($variation['stream_audio_url'] ?? 'none') . "</p>";
if (!empty($variationAudioUrl)) {
// Download variation audio
$localVariationUrl = downloadAndStoreAudio($variationAudioUrl, $actualTaskId, 'variation', $variationIndex);
// Extract variation title
$variationTitle = $variation['title'] ?? null;
if (!$variationTitle) {
$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 " . ($variationIndex + 1);
}
}
// Build variation metadata
$variationMetadata = [
'genre' => $variation['genre'] ?? (is_array($variation['tags']) ? $variation['tags'][0] : (is_string($variation['tags']) ? explode(',', $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
];
// Download variation image if available
$variationImageUrl = null;
if (isset($variation['image_url']) && !empty($variation['image_url'])) {
$variationImageUrl = downloadAndStoreImage($variation['image_url'], $actualTaskId . '_var' . $variationIndex);
} elseif (isset($variation['source_image_url']) && !empty($variation['source_image_url'])) {
$variationImageUrl = downloadAndStoreImage($variation['source_image_url'], $actualTaskId . '_var' . $variationIndex);
}
// Insert variation
try {
$variationStmt = $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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
");
$variationTags = is_array($variation['tags']) ? implode(', ', $variation['tags']) : ($variation['tags'] ?? '');
// Ensure metadata is valid JSON and not too large
$variationMetadataJson = json_encode($variationMetadata);
if (json_last_error() !== JSON_ERROR_NONE) {
echo "<p style='color: red;'>â Invalid JSON in variation metadata: " . json_last_error_msg() . "</p>";
$variationMetadataJson = json_encode(['title' => $variationTitle, 'duration' => $variation['duration'] ?? null]);
}
$insertParams = [
$trackIdForVariations,
$variationIndex,
$localVariationUrl ?: $variationAudioUrl,
$variation['duration'] ?? null,
$variationTitle,
$variationTags ?: null,
$variationImageUrl,
$variation['source_audio_url'] ?? null,
$variation['stream_audio_url'] ?? null,
$variationMetadataJson
];
echo "<p>Inserting variation with params: track_id={$trackIdForVariations}, index={$variationIndex}, audio=" . substr($insertParams[2], 0, 50) . "...</p>";
$insertResult = $variationStmt->execute($insertParams);
if ($insertResult) {
$storedVariationsCount++;
$variationIndex++;
echo "<p style='color: green;'>â
Stored variation {$variationIndex}: {$variationTitle}</p>";
} else {
$errorInfo = $variationStmt->errorInfo();
echo "<p style='color: red;'>â Failed to insert variation: " . htmlspecialchars(json_encode($errorInfo)) . "</p>";
echo "<p>SQL: " . htmlspecialchars($variationStmt->queryString) . "</p>";
}
} catch (Exception $e) {
echo "<p style='color: red;'>â Exception inserting variation: " . htmlspecialchars($e->getMessage()) . "</p>";
echo "<p>Trace: " . htmlspecialchars($e->getTraceAsString()) . "</p>";
}
} else {
echo "<p style='color: orange;'>â ī¸ Skipping variation {$originalIndex} - no audio URL</p>";
}
}
// Update variations_count on main track - CRITICAL for variation button to show
if ($storedVariationsCount > 0) {
$updateVarCountStmt = $pdo->prepare("UPDATE music_tracks SET variations_count = ?, selected_variation = 0 WHERE id = ?");
$updateVarCountStmt->execute([$storedVariationsCount, $trackIdForVariations]);
// Verify the update
$verifyCountStmt = $pdo->prepare("SELECT variations_count FROM music_tracks WHERE id = ?");
$verifyCountStmt->execute([$trackIdForVariations]);
$updatedCount = $verifyCountStmt->fetchColumn();
echo "<p style='color: green; font-weight: bold;'>â
Stored {$storedVariationsCount} variation(s) and updated variations_count to {$updatedCount}</p>";
// Double-check: count actual variations in database
$actualCountStmt = $pdo->prepare("SELECT COUNT(*) FROM audio_variations WHERE track_id = ?");
$actualCountStmt->execute([$trackIdForVariations]);
$actualCount = $actualCountStmt->fetchColumn();
echo "<p>Verified: {$actualCount} variation(s) actually stored in audio_variations table for track #{$trackIdForVariations}</p>";
if ($actualCount != $storedVariationsCount) {
echo "<p style='color: red;'>â ī¸ WARNING: Count mismatch! Expected {$storedVariationsCount} but found {$actualCount} in database</p>";
}
} else {
echo "<p style='color: orange;'>â ī¸ No variations were stored (all items may have had empty audio URLs)</p>";
}
}
$fixedCount++;
$logEntry = "â
Recovered track #{$trackId} from local files/task_results (404 on API)\n";
file_put_contents($logFile, $logEntry, FILE_APPEND | LOCK_EX);
} else {
// Try direct SQL update as fallback
echo "<p style='color: orange;'>â ī¸ updateMusicTrack failed, trying direct SQL update...</p>";
try {
$directUpdates = ['status = ?'];
$directParams = ['complete'];
if ($localAudioUrl) {
$directUpdates[] = 'audio_url = ?';
$directParams[] = $localAudioUrl;
}
if ($metadata) {
$directUpdates[] = 'metadata = ?';
$directParams[] = $metadata;
}
if ($duration !== null) {
$directUpdates[] = 'duration = ?';
$directParams[] = $duration;
}
if (!empty($lyrics)) {
$directUpdates[] = 'lyrics = ?';
$directParams[] = $lyrics;
}
if ($tags) {
$directUpdates[] = 'tags = ?';
$directParams[] = is_array($tags) ? implode('; ', $tags) : $tags;
}
if ($localImageUrl) {
$directUpdates[] = 'image_url = ?';
$directParams[] = $localImageUrl;
}
$directUpdates[] = 'updated_at = NOW()';
$directParams[] = $actualTaskId;
$directSql = "UPDATE music_tracks SET " . implode(', ', $directUpdates) . " WHERE task_id = ?";
$directStmt = $pdo->prepare($directSql);
$directResult = $directStmt->execute($directParams);
if ($directResult) {
$rowsAffected = $directStmt->rowCount();
if ($rowsAffected > 0) {
echo "<p style='color: green; font-weight: bold;'>â
Successfully recovered via direct SQL update!</p>";
$fixedCount++;
$logEntry = "â
Recovered track #{$trackId} via direct SQL (404 on API)\n";
file_put_contents($logFile, $logEntry, FILE_APPEND | LOCK_EX);
} else {
echo "<p style='color: red;'>â Direct SQL update executed but no rows affected (task_id may not match)</p>";
echo "<p>Debug: task_id = {$taskId}, track_id = {$trackId}</p>";
// Try updating by track ID instead
$directParams[count($directParams) - 1] = $trackId; // Replace last param (task_id) with track id
$directSqlById = "UPDATE music_tracks SET " . implode(', ', $directUpdates) . " WHERE id = ?";
$directStmtById = $pdo->prepare($directSqlById);
$directResultById = $directStmtById->execute($directParams);
if ($directResultById && $directStmtById->rowCount() > 0) {
echo "<p style='color: green; font-weight: bold;'>â
Successfully recovered via track ID update!</p>";
$fixedCount++;
} else {
echo "<p style='color: red;'>â Failed to update by track ID as well</p>";
$errorInfo = $directStmtById->errorInfo();
echo "<p>SQL Error: " . htmlspecialchars(json_encode($errorInfo)) . "</p>";
$errorCount++;
}
}
} else {
$errorInfo = $directStmt->errorInfo();
echo "<p style='color: red;'>â Direct SQL update failed</p>";
echo "<p>SQL Error: " . htmlspecialchars(json_encode($errorInfo)) . "</p>";
echo "<p>SQL: " . htmlspecialchars($directSql) . "</p>";
$errorCount++;
}
} catch (Exception $e) {
echo "<p style='color: red;'>â Exception during direct SQL update: " . htmlspecialchars($e->getMessage()) . "</p>";
$errorCount++;
}
}
} else {
// No local files or task results - mark as failed and refund
echo "<p style='color: red;'>â No local files or task results found - marking as failed</p>";
$error_metadata = json_encode([
'code' => 404,
'msg' => 'Track generation incomplete',
'error_type' => 'not_found',
'timestamp' => date('Y-m-d H:i:s'),
'fixed_by' => 'fix_stuck_processing_tracks'
]);
$update_stmt = $pdo->prepare("UPDATE music_tracks SET status = 'failed', metadata = ?, updated_at = NOW() WHERE id = ?");
if ($update_stmt->execute([$error_metadata, $trackId])) {
// Refund credit
$refund_stmt = $pdo->prepare("UPDATE users SET credits = credits + 1 WHERE id = ?");
$refund_stmt->execute([$track['user_id']]);
// Record refund transaction
$check_stmt = $pdo->prepare("SELECT COUNT(*) FROM credit_transactions WHERE user_id = ? AND type = 'refund' AND description LIKE ?");
$check_stmt->execute([$track['user_id'], "%Track #{$trackId}%"]);
if ($check_stmt->fetchColumn() == 0) {
$trans_stmt = $pdo->prepare("
INSERT INTO credit_transactions (user_id, amount, type, description, created_at)
VALUES (?, 1, 'refund', 'Track #{$trackId} incomplete - refunded', NOW())
");
$trans_stmt->execute([$track['user_id']]);
}
echo "<p style='color: green;'>â
Marked as failed and refunded 1 credit</p>";
$failedCount++;
} else {
echo "<p style='color: red;'>â Failed to update database</p>";
$errorCount++;
}
}
} else {
$errorCount++;
}
echo "</div>";
continue;
}
$api_data = json_decode($response, true);
if (!$api_data) {
echo "<p style='color: red;'>â Invalid JSON response from API</p>";
$errorCount++;
echo "</div>";
continue;
}
// Check for error codes
if (isset($api_data['code']) && ($api_data['code'] == 400 || $api_data['code'] == 531 || $api_data['code'] >= 400)) {
echo "<p style='color: red;'>â API returned error code: {$api_data['code']}</p>";
// Sanitize error message - remove API.Box references only
$api_error_msg = $api_data['msg'] ?? 'Generation failed';
$sanitized_error = preg_replace('/\b(API\.Box|api\.box|API\.box|not found on API\.Box|not found in API\.Box|Task not found in API\.Box|Track.*not found on API\.Box)\b/i', '', $api_error_msg);
$sanitized_error = trim($sanitized_error);
$sanitized_error = preg_replace('/\s+/', ' ', $sanitized_error);
if (empty($sanitized_error)) {
$sanitized_error = 'Generation failed';
}
echo "<p><strong>Error:</strong> " . htmlspecialchars($sanitized_error) . "</p>";
// Mark as failed
$error_metadata = json_encode([
'code' => $api_data['code'],
'msg' => $sanitized_error,
'error_type' => ($api_data['code'] == 400) ? 'content_violation' : 'generation_failed',
'data' => $api_data,
'timestamp' => date('Y-m-d H:i:s'),
'fixed_by' => 'fix_stuck_processing_tracks'
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$update_stmt = $pdo->prepare("UPDATE music_tracks SET status = 'failed', metadata = ?, updated_at = NOW() WHERE id = ?");
if ($update_stmt->execute([$error_metadata, $trackId])) {
echo "<p style='color: green;'>â
Updated track to 'failed' status</p>";
// Check if credit was already refunded
$check_stmt = $pdo->prepare("
SELECT COUNT(*)
FROM credit_transactions
WHERE user_id = ?
AND type = 'refund'
AND (description LIKE ? OR description LIKE ? OR description LIKE ?)
");
$check_stmt->execute([
$track['user_id'],
"%Track failed: {$trackId}%",
"%Track #{$trackId}%",
"%track {$trackId}%"
]);
$already_refunded = $check_stmt->fetchColumn() > 0;
if (!$already_refunded) {
// Refund credit
$refund_stmt = $pdo->prepare("UPDATE users SET credits = credits + 1 WHERE id = ?");
if ($refund_stmt->execute([$track['user_id']])) {
// Record refund transaction
$trans_stmt = $pdo->prepare("
INSERT INTO credit_transactions (user_id, amount, type, description, created_at)
VALUES (?, 1, 'refund', 'Track failed (stuck fix): {$trackId} - {$track['title']}', NOW())
");
$trans_stmt->execute([$track['user_id']]);
echo "<p style='color: green;'>â
Refunded 1 credit</p>";
}
} else {
echo "<p style='color: blue;'>âšī¸ Credit already refunded</p>";
}
$failedCount++;
} else {
echo "<p style='color: red;'>â Failed to update database</p>";
}
echo "</div>";
continue;
}
// Get API status
$api_status = $api_data['status'] ?? null;
// Check if it's complete
if ($api_status === 'complete' || $api_status === 'completed' ||
(isset($api_data['data']['callbackType']) && $api_data['data']['callbackType'] === 'complete')) {
echo "<p style='color: green;'>â
Track is COMPLETE - processing...</p>";
// Simulate callback processing
// We need to format the API response like a callback would be
$callbackData = $api_data;
// If the API response has a different structure, normalize it
if (isset($api_data['data']) && !isset($callbackData['task_id'])) {
$callbackData['task_id'] = $taskId;
$callbackData['status'] = 'complete';
}
// Log the fix attempt
$logEntry = "[" . date('Y-m-d H:i:s') . "] Fixing stuck track #{$trackId} (task_id: {$taskId})\n";
$logEntry .= "API Status: complete\n";
file_put_contents($logFile, $logEntry, FILE_APPEND | LOCK_EX);
// Process like a callback - we'll use the callback processing logic
// But we need to call it in a way that works
// Get original track data for metadata extraction
$originalData = getTrackOriginalData($taskId);
$originalPrompt = $originalData['prompt'] ?? $track['prompt'];
// Extract comprehensive metadata
$enhanced_metadata = extractComprehensiveMetadata($callbackData, $originalPrompt);
// Extract title, duration, tags, model_name, lyrics
$apiTitle = extractTitleFromCallback($callbackData);
$title = $track['title'] ?: $apiTitle;
$duration = extractDurationFromCallback($callbackData);
$tagsRaw = extractTagsFromCallback($callbackData);
$tags = formatTagsForStorage($tagsRaw);
$modelName = extractModelNameFromCallback($callbackData);
// Extract lyrics
$lyrics = '';
if (isset($callbackData['data']['data']) && is_array($callbackData['data']['data'])) {
foreach ($callbackData['data']['data'] as $item) {
if (isset($item['prompt']) && !empty($item['prompt'])) {
$lyrics = $item['prompt'];
break;
}
}
}
if ($lyrics) {
$lyrics = str_replace(['[Verse]', '[Chorus]', '[Bridge]', '[Outro]', '[Intro]'], '', $lyrics);
$lyrics = preg_replace('/\[.*?\]/', '', $lyrics);
$lyrics = trim($lyrics);
}
// Extract and download image
$imageUrl = extractImageUrlFromCallback($callbackData);
$localImageUrl = null;
if ($imageUrl) {
$localImageUrl = downloadAndStoreImage($imageUrl, $taskId);
}
// Extract audio URL - check multiple possible locations
$audioUrl = null;
if (isset($callbackData['data']['data']) && is_array($callbackData['data']['data'])) {
foreach ($callbackData['data']['data'] as $audio) {
$audioUrl = (!empty($audio['audio_url'])) ? $audio['audio_url'] :
((!empty($audio['source_audio_url'])) ? $audio['source_audio_url'] :
((!empty($audio['stream_audio_url'])) ? $audio['stream_audio_url'] : null));
if ($audioUrl) break;
}
} else {
$audioUrl = $callbackData['audio_url'] ??
$callbackData['result']['audio_url'] ??
$callbackData['data']['audio_url'] ?? null;
}
$localAudioUrl = null;
if ($audioUrl) {
$localAudioUrl = downloadAndStoreAudio($audioUrl, $taskId, 'main');
}
// Store tags in metadata
if ($tags) {
$enhanced_metadata['tags'] = $tags;
}
$metadata = json_encode($enhanced_metadata);
// Update the track
$updateResult = updateMusicTrack(
$taskId,
'complete',
$localAudioUrl ?: $audioUrl,
null,
!empty($lyrics) ? $lyrics : null,
$metadata,
$duration,
$title,
$tags,
$modelName,
$localImageUrl
);
if ($updateResult) {
echo "<p style='color: green;'>â
Successfully updated track to 'complete'!</p>";
if ($localAudioUrl) {
echo "<p>â
Audio downloaded locally: {$localAudioUrl}</p>";
} elseif ($audioUrl) {
echo "<p>â ī¸ Using external audio URL: {$audioUrl}</p>";
}
if ($localImageUrl) {
echo "<p>â
Image downloaded locally: {$localImageUrl}</p>";
}
if ($lyrics) {
echo "<p>â
Lyrics extracted (" . strlen($lyrics) . " chars)</p>";
}
// Process variations from API response
if (isset($api_data['data']['data']) && is_array($api_data['data']['data']) && count($api_data['data']['data']) > 1) {
echo "<p>đĩ Processing variations from API response...</p>";
// Get the track ID
$trackIdForVariations = $trackId;
// Clear existing variations
$clearStmt = $pdo->prepare("DELETE FROM audio_variations WHERE track_id = ?");
$clearStmt->execute([$trackIdForVariations]);
$storedVariationsCount = 0;
$variationIndex = 0;
$audioData = $api_data['data']['data'];
foreach ($audioData as $originalIndex => $variation) {
// Check for audio URL
$variationAudioUrl = (!empty($variation['audio_url'])) ? $variation['audio_url'] :
((!empty($variation['source_audio_url'])) ? $variation['source_audio_url'] :
((!empty($variation['stream_audio_url'])) ? $variation['stream_audio_url'] : null));
if (!empty($variationAudioUrl)) {
// Download variation audio
$localVariationUrl = downloadAndStoreAudio($variationAudioUrl, $taskId, 'variation', $variationIndex);
// Extract variation title
$variationTitle = $variation['title'] ?? null;
if (!$variationTitle) {
$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 " . ($variationIndex + 1);
}
}
// Build variation metadata
$variationMetadata = [
'genre' => $variation['genre'] ?? (is_array($variation['tags']) ? $variation['tags'][0] : (is_string($variation['tags']) ? explode(',', $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
];
// Download variation image if available
$variationImageUrl = null;
if (isset($variation['image_url']) && !empty($variation['image_url'])) {
$variationImageUrl = downloadAndStoreImage($variation['image_url'], $taskId . '_var' . $variationIndex);
} elseif (isset($variation['source_image_url']) && !empty($variation['source_image_url'])) {
$variationImageUrl = downloadAndStoreImage($variation['source_image_url'], $taskId . '_var' . $variationIndex);
}
// Insert variation
try {
$variationStmt = $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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
");
$variationTags = is_array($variation['tags']) ? implode(', ', $variation['tags']) : $variation['tags'];
$insertResult = $variationStmt->execute([
$trackIdForVariations,
$variationIndex,
$localVariationUrl ?: $variationAudioUrl,
$variation['duration'] ?? null,
$variationTitle,
$variationTags,
$variationImageUrl,
$variation['source_audio_url'] ?? null,
$variation['stream_audio_url'] ?? null,
json_encode($variationMetadata)
]);
if ($insertResult) {
$storedVariationsCount++;
$variationIndex++;
echo "<p style='color: green;'>â
Stored variation {$variationIndex}: {$variationTitle}</p>";
} else {
$errorInfo = $variationStmt->errorInfo();
echo "<p style='color: red;'>â Failed to insert variation: " . htmlspecialchars(json_encode($errorInfo)) . "</p>";
}
} catch (Exception $e) {
echo "<p style='color: red;'>â Exception inserting variation: " . htmlspecialchars($e->getMessage()) . "</p>";
}
} else {
echo "<p style='color: orange;'>â ī¸ Skipping variation {$originalIndex} - no audio URL</p>";
}
}
// Update variations_count on main track - CRITICAL: This is what makes the variation button show!
if ($storedVariationsCount > 0) {
$updateVarCountStmt = $pdo->prepare("UPDATE music_tracks SET variations_count = ?, selected_variation = 0 WHERE id = ?");
$updateVarCountStmt->execute([$storedVariationsCount, $trackIdForVariations]);
// Verify the update worked
$verifyCountStmt = $pdo->prepare("SELECT variations_count FROM music_tracks WHERE id = ?");
$verifyCountStmt->execute([$trackIdForVariations]);
$updatedCount = $verifyCountStmt->fetchColumn();
// Double-check: count actual variations in database
$actualCountStmt = $pdo->prepare("SELECT COUNT(*) FROM audio_variations WHERE track_id = ?");
$actualCountStmt->execute([$trackIdForVariations]);
$actualCount = $actualCountStmt->fetchColumn();
echo "<p style='color: green; font-weight: bold;'>â
Stored {$storedVariationsCount} variation(s)</p>";
echo "<p>đ <strong>variations_count field:</strong> {$updatedCount}</p>";
echo "<p>đ <strong>Actual variations in DB:</strong> {$actualCount}</p>";
if ($actualCount == $storedVariationsCount && $updatedCount == $storedVariationsCount) {
echo "<p style='color: green;'>â
Variation button should now show! (variations_count = {$updatedCount})</p>";
} else {
echo "<p style='color: red;'>â ī¸ MISMATCH: Expected {$storedVariationsCount}, DB has {$actualCount}, field shows {$updatedCount}</p>";
// Fix the count if wrong
if ($actualCount != $updatedCount) {
$fixCountStmt = $pdo->prepare("UPDATE music_tracks SET variations_count = ? WHERE id = ?");
$fixCountStmt->execute([$actualCount, $trackIdForVariations]);
echo "<p style='color: green;'>â
Fixed variations_count to match actual count: {$actualCount}</p>";
}
}
} else {
echo "<p style='color: orange;'>â ī¸ No variations stored (all items may have had empty audio URLs)</p>";
}
}
$fixedCount++;
$logEntry = "â
Successfully fixed track #{$trackId}\n";
file_put_contents($logFile, $logEntry, FILE_APPEND | LOCK_EX);
} else {
echo "<p style='color: red;'>â Failed to update track in database</p>";
$errorCount++;
}
} elseif ($api_status === 'processing') {
echo "<p style='color: orange;'>âŗ Still processing</p>";
$stillProcessingCount++;
} elseif ($api_status === 'failed' || $api_status === 'error' || $api_status === 'rejected') {
echo "<p style='color: red;'>â Track failed</p>";
// Sanitize error message - remove API.Box references only
$api_error_msg = $api_data['error'] ?? $api_data['msg'] ?? $api_data['error_msg'] ?? 'Generation failed';
$sanitized_error = preg_replace('/\b(API\.Box|api\.box|API\.box|not found on API\.Box|not found in API\.Box|Task not found in API\.Box|Track.*not found on API\.Box)\b/i', '', $api_error_msg);
$sanitized_error = trim($sanitized_error);
$sanitized_error = preg_replace('/\s+/', ' ', $sanitized_error);
if (empty($sanitized_error)) {
$sanitized_error = 'Generation failed';
}
// Mark as failed
$error_metadata = json_encode([
'code' => $api_data['code'] ?? 531,
'msg' => $sanitized_error,
'error_type' => 'generation_failed',
'data' => $api_data,
'timestamp' => date('Y-m-d H:i:s'),
'fixed_by' => 'fix_stuck_processing_tracks'
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$update_stmt = $pdo->prepare("UPDATE music_tracks SET status = 'failed', metadata = ?, updated_at = NOW() WHERE id = ?");
if ($update_stmt->execute([$error_metadata, $trackId])) {
echo "<p style='color: green;'>â
Updated track to 'failed' status</p>";
// Check if credit was already refunded
$check_stmt = $pdo->prepare("
SELECT COUNT(*)
FROM credit_transactions
WHERE user_id = ?
AND type = 'refund'
AND (description LIKE ? OR description LIKE ? OR description LIKE ?)
");
$check_stmt->execute([
$track['user_id'],
"%Track failed: {$trackId}%",
"%Track #{$trackId}%",
"%track {$trackId}%"
]);
$already_refunded = $check_stmt->fetchColumn() > 0;
if (!$already_refunded) {
// Refund credit
$refund_stmt = $pdo->prepare("UPDATE users SET credits = credits + 1 WHERE id = ?");
if ($refund_stmt->execute([$track['user_id']])) {
// Record refund transaction
$trans_stmt = $pdo->prepare("
INSERT INTO credit_transactions (user_id, amount, type, description, created_at)
VALUES (?, 1, 'refund', 'Track failed (stuck fix): {$trackId} - {$track['title']}', NOW())
");
$trans_stmt->execute([$track['user_id']]);
echo "<p style='color: green;'>â
Refunded 1 credit</p>";
}
} else {
echo "<p style='color: blue;'>âšī¸ Credit already refunded</p>";
}
$failedCount++;
}
} else {
echo "<p style='color: orange;'>â ī¸ Unknown API status: " . htmlspecialchars($api_status ?? 'N/A') . "</p>";
echo "<pre>" . htmlspecialchars(json_encode($api_data, JSON_PRETTY_PRINT)) . "</pre>";
$errorCount++;
}
echo "</div>";
}
echo "<hr>";
echo "<h2>đ Summary</h2>";
echo "<p><strong>Fixed (processing â complete):</strong> {$fixedCount}</p>";
echo "<p><strong>Still Processing:</strong> {$stillProcessingCount}</p>";
echo "<p><strong>Marked as Failed:</strong> {$failedCount}</p>";
echo "<p><strong>Errors:</strong> {$errorCount}</p>";
echo "<p><strong>Total Checked:</strong> " . count($stuckTracks) . "</p>";
if ($fixedCount > 0) {
echo "<p style='color: green; font-size: 18px; font-weight: bold;'>â
Successfully fixed {$fixedCount} track(s)!</p>";
}
echo "<p><a href='admin.php?tab=track-status'>â Back to Admin</a></p>";