![]() 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/admin/ |
<?php
session_start();
// Check if user is admin
if (!isset($_SESSION['user_id']) || $_SESSION['role'] !== 'admin') {
header('Location: login.php');
exit;
}
require_once '../config/database.php';
$pdo = getDBConnection();
// Handle form submissions
$message = '';
$action = $_POST['action'] ?? $_GET['action'] ?? '';
// 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;
// Skip if file already exists
if (file_exists($localPath)) {
return $webPath;
}
// 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_sync',
'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;
}
// Function to extract comprehensive metadata from existing data
function extractComprehensiveMetadata($data) {
return [
// Raw callback data for debugging
'raw_callback' => $data,
// Basic music information
'genre' => $data['genre'] ?? $data['tags'][0] ?? 'Electronic',
'style' => $data['style'] ?? '',
'tags' => $data['tags'] ?? [],
'bpm' => $data['bpm'] ?? $data['tempo'] ?? 120,
'key' => $data['key'] ?? 'C major',
'time_signature' => $data['time_signature'] ?? '4/4',
'mood' => $data['mood'] ?? 'neutral',
'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',
'sync_processed' => date('Y-m-d H:i:s'),
'api_version' => $data['api_version'] ?? null,
'api_endpoint' => $data['api_endpoint'] ?? null
]
];
}
if ($action === 'sync_all_metadata') {
try {
// Get all tracks with existing metadata
$stmt = $pdo->query("
SELECT id, title, metadata, task_id, audio_url, video_url, status
FROM music_tracks
WHERE status = 'complete'
AND metadata IS NOT NULL
AND metadata != ''
");
$tracks = $stmt->fetchAll();
$updated_count = 0;
$processed_count = 0;
$files_downloaded = 0;
foreach ($tracks as $track) {
$processed_count++;
$metadata = json_decode($track['metadata'], true);
if (!$metadata) {
continue;
}
// Extract comprehensive metadata
$enhanced_metadata = extractComprehensiveMetadata($metadata);
// Download audio files locally
$localAudioUrl = null;
$localVideoUrl = null;
if ($track['audio_url'] && strpos($track['audio_url'], 'http') === 0) {
$localAudioUrl = downloadAndStoreAudio($track['audio_url'], $track['task_id'], 'main');
if ($localAudioUrl) {
$files_downloaded++;
}
}
if ($track['video_url'] && strpos($track['video_url'], 'http') === 0) {
$localVideoUrl = downloadAndStoreAudio($track['video_url'], $track['task_id'], 'video');
if ($localVideoUrl) {
$files_downloaded++;
}
}
// Update individual metadata fields
$updates = [];
$params = [];
// Update main metadata
$updates[] = 'metadata = ?';
$params[] = json_encode($enhanced_metadata);
// Update individual fields
if (isset($enhanced_metadata['audio_quality'])) {
$updates[] = 'audio_quality = ?';
$params[] = json_encode($enhanced_metadata['audio_quality']);
}
if (isset($enhanced_metadata['generation_parameters'])) {
$updates[] = 'generation_parameters = ?';
$params[] = json_encode($enhanced_metadata['generation_parameters']);
}
if (isset($enhanced_metadata['processing_info'])) {
$updates[] = 'processing_info = ?';
$params[] = json_encode($enhanced_metadata['processing_info']);
}
if (isset($enhanced_metadata['cost_info'])) {
$updates[] = 'cost_info = ?';
$params[] = json_encode($enhanced_metadata['cost_info']);
}
if (isset($enhanced_metadata['waveform_data'])) {
$updates[] = 'waveform_data = ?';
$params[] = json_encode($enhanced_metadata['waveform_data']);
}
if (isset($enhanced_metadata['spectrum_analysis'])) {
$updates[] = 'spectrum_analysis = ?';
$params[] = json_encode($enhanced_metadata['spectrum_analysis']);
}
if (isset($enhanced_metadata['audio_segments'])) {
$updates[] = 'audio_segments = ?';
$params[] = json_encode($enhanced_metadata['audio_segments']);
}
if (isset($enhanced_metadata['error_details'])) {
$updates[] = 'error_details = ?';
$params[] = json_encode($enhanced_metadata['error_details']);
}
if (isset($enhanced_metadata['audio_analysis'])) {
$updates[] = 'audio_analysis = ?';
$params[] = json_encode($enhanced_metadata['audio_analysis']);
}
if (isset($enhanced_metadata['system_info'])) {
$updates[] = 'system_info = ?';
$params[] = json_encode($enhanced_metadata['system_info']);
}
// Update audio URLs if local files were downloaded
if ($localAudioUrl) {
$updates[] = 'audio_url = ?';
$params[] = $localAudioUrl;
}
if ($localVideoUrl) {
$updates[] = 'video_url = ?';
$params[] = $localVideoUrl;
}
$params[] = $track['id'];
$sql = "UPDATE music_tracks SET " . implode(', ', $updates) . ", updated_at = NOW() WHERE id = ?";
$update_stmt = $pdo->prepare($sql);
$update_stmt->execute($params);
$updated_count++;
}
$message = "â
Sync completed! Processed: $processed_count, Updated: $updated_count, Files Downloaded: $files_downloaded";
} catch (Exception $e) {
$message = "â Error during sync: " . $e->getMessage();
}
}
// Get statistics
$stats = [];
try {
// Total tracks
$stmt = $pdo->query("SELECT COUNT(*) as total FROM music_tracks WHERE status = 'complete'");
$stats['total_tracks'] = $stmt->fetch()['total'];
// Tracks with metadata
$stmt = $pdo->query("SELECT COUNT(*) as total FROM music_tracks WHERE status = 'complete' AND metadata IS NOT NULL AND metadata != ''");
$stats['tracks_with_metadata'] = $stmt->fetch()['total'];
// Tracks with local files
$stmt = $pdo->query("SELECT COUNT(*) as total FROM music_tracks WHERE status = 'complete' AND audio_url LIKE '/audio_files/%'");
$stats['tracks_with_local_files'] = $stmt->fetch()['total'];
// Tracks with enhanced metadata
$stmt = $pdo->query("SELECT COUNT(*) as total FROM music_tracks WHERE status = 'complete' AND audio_quality IS NOT NULL");
$stats['tracks_with_enhanced_metadata'] = $stmt->fetch()['total'];
} catch (Exception $e) {
$stats['error'] = $e->getMessage();
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Comprehensive Metadata Sync - Admin</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<style>
:root {
--primary: #667eea;
--secondary: #764ba2;
--accent: #4facfe;
--bg-primary: #0a0a0a;
--bg-secondary: #1a1a1a;
--bg-card: rgba(26, 26, 26, 0.9);
--text-primary: #ffffff;
--text-secondary: #a0aec0;
--border-light: rgba(255, 255, 255, 0.1);
--shadow-medium: 0 10px 15px -3px rgba(0, 0, 0, 0.3);
--success: #48bb78;
--error: #f56565;
--warning: #ed8936;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: var(--bg-primary);
color: var(--text-primary);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
line-height: 1.6;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 40px 20px;
}
.header {
text-align: center;
margin-bottom: 40px;
}
.header h1 {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
font-size: 36px;
margin-bottom: 10px;
}
.header p {
color: var(--text-secondary);
font-size: 18px;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin: 30px 0;
}
.stat-card {
background: var(--bg-card);
border: 1px solid var(--border-light);
border-radius: 12px;
padding: 25px;
text-align: center;
transition: all 0.3s ease;
}
.stat-card:hover {
transform: translateY(-2px);
border-color: var(--primary);
}
.stat-number {
font-size: 2.5em;
font-weight: bold;
color: var(--success);
margin-bottom: 10px;
}
.stat-label {
font-size: 1em;
color: var(--text-secondary);
font-weight: 500;
}
.sync-section {
background: var(--bg-card);
border: 1px solid var(--border-light);
border-radius: 12px;
padding: 30px;
margin: 30px 0;
}
.sync-button {
background: linear-gradient(135deg, var(--success), #38a169);
color: white;
border: none;
padding: 15px 30px;
border-radius: 8px;
cursor: pointer;
font-size: 1.1em;
font-weight: 600;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 10px;
}
.sync-button:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-medium);
}
.message {
padding: 20px;
border-radius: 12px;
margin: 20px 0;
font-weight: 500;
}
.success {
background: rgba(72, 187, 120, 0.2);
border: 1px solid var(--success);
color: var(--success);
}
.error {
background: rgba(245, 101, 101, 0.2);
border: 1px solid var(--error);
color: var(--error);
}
.info {
background: rgba(102, 126, 234, 0.2);
border: 1px solid var(--primary);
color: var(--primary);
}
.warning {
background: rgba(237, 137, 54, 0.2);
border: 1px solid var(--warning);
color: var(--warning);
}
.info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
margin: 20px 0;
}
.info-card {
background: rgba(255, 255, 255, 0.05);
padding: 20px;
border-radius: 8px;
border: 1px solid var(--border-light);
}
.info-card h3 {
color: var(--accent);
margin-bottom: 15px;
font-size: 1.2em;
}
.info-card ul {
list-style: none;
padding: 0;
}
.info-card li {
padding: 8px 0;
border-bottom: 1px solid var(--border-light);
display: flex;
align-items: center;
gap: 10px;
}
.info-card li:last-child {
border-bottom: none;
}
.back-link {
display: inline-flex;
align-items: center;
gap: 10px;
margin-top: 30px;
color: var(--accent);
text-decoration: none;
font-weight: 600;
padding: 10px 20px;
background: var(--bg-card);
border: 1px solid var(--border-light);
border-radius: 8px;
transition: all 0.3s ease;
}
.back-link:hover {
border-color: var(--accent);
transform: translateY(-2px);
}
.progress-bar {
width: 100%;
height: 8px;
background: var(--bg-secondary);
border-radius: 4px;
overflow: hidden;
margin: 20px 0;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, var(--success), var(--accent));
transition: width 0.3s ease;
}
.sync-controls {
display: flex;
gap: 15px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.sync-button.secondary {
background: linear-gradient(135deg, var(--primary), var(--secondary));
}
.sync-button.stop {
background: linear-gradient(135deg, var(--error), #c53030);
}
.progress-stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 15px;
margin: 20px 0;
}
.stat-item {
background: rgba(255, 255, 255, 0.05);
padding: 15px;
border-radius: 8px;
text-align: center;
}
.stat-label {
display: block;
font-size: 0.9em;
color: var(--text-secondary);
margin-bottom: 5px;
}
.stat-value {
display: block;
font-size: 1.5em;
font-weight: bold;
color: var(--success);
}
.status-display {
background: var(--bg-card);
border: 1px solid var(--border-light);
border-radius: 8px;
padding: 20px;
margin: 20px 0;
}
.status-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 0;
border-bottom: 1px solid var(--border-light);
}
.status-item:last-child {
border-bottom: none;
}
.status-label {
font-weight: 600;
color: var(--text-secondary);
}
.status-value {
font-weight: bold;
color: var(--accent);
}
.sync-log, .sync-summary {
background: var(--bg-card);
border: 1px solid var(--border-light);
border-radius: 8px;
padding: 20px;
margin: 20px 0;
}
.log-content, .summary-content {
max-height: 300px;
overflow-y: auto;
background: rgba(0, 0, 0, 0.3);
padding: 15px;
border-radius: 5px;
font-family: monospace;
font-size: 0.9em;
}
.log-entry {
padding: 5px 0;
border-bottom: 1px solid var(--border-light);
}
.log-entry:last-child {
border-bottom: none;
}
.log-success { color: var(--success); }
.log-error { color: var(--error); }
.log-info { color: var(--accent); }
.log-warning { color: var(--warning); }
@media (max-width: 768px) {
.stats-grid {
grid-template-columns: repeat(2, 1fr);
}
.info-grid {
grid-template-columns: 1fr;
}
.sync-controls {
flex-direction: column;
}
.progress-stats {
grid-template-columns: repeat(2, 1fr);
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>đ Comprehensive Metadata Sync</h1>
<p>Sync all tracks with enhanced metadata and local file storage</p>
</div>
<?php if ($message): ?>
<div class="message <?= strpos($message, 'â
') !== false ? 'success' : 'error' ?>">
<i class="fas <?= strpos($message, 'â
') !== false ? 'fa-check-circle' : 'fa-exclamation-triangle' ?>"></i>
<?= htmlspecialchars($message) ?>
</div>
<?php endif; ?>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-number"><?= number_format($stats['total_tracks'] ?? 0) ?></div>
<div class="stat-label">Total Tracks</div>
</div>
<div class="stat-card">
<div class="stat-number"><?= number_format($stats['tracks_with_metadata'] ?? 0) ?></div>
<div class="stat-label">With Metadata</div>
</div>
<div class="stat-card">
<div class="stat-number"><?= number_format($stats['tracks_with_local_files'] ?? 0) ?></div>
<div class="stat-label">Local Files</div>
</div>
<div class="stat-card">
<div class="stat-number"><?= number_format($stats['tracks_with_enhanced_metadata'] ?? 0) ?></div>
<div class="stat-label">Enhanced Metadata</div>
</div>
</div>
<div class="sync-section">
<h2><i class="fas fa-sync-alt"></i> Sync Actions</h2>
<!-- Sync Control Buttons -->
<div class="sync-controls">
<button id="startSync" class="sync-button">
<i class="fas fa-download"></i>
đ Start Async Sync
</button>
<button id="stopSync" class="sync-button stop" style="display: none;">
<i class="fas fa-stop"></i>
âšī¸ Stop Sync
</button>
<button id="refreshStats" class="sync-button secondary">
<i class="fas fa-refresh"></i>
đ Refresh Stats
</button>
</div>
<!-- Progress Section -->
<div id="syncProgress" style="display: none;">
<div class="progress-bar">
<div id="progressFill" class="progress-fill" style="width: 0%"></div>
</div>
<div class="progress-stats">
<div class="stat-item">
<span class="stat-label">Processed:</span>
<span id="processedCount" class="stat-value">0</span>
</div>
<div class="stat-item">
<span class="stat-label">Updated:</span>
<span id="updatedCount" class="stat-value">0</span>
</div>
<div class="stat-item">
<span class="stat-label">Files Downloaded:</span>
<span id="filesDownloaded" class="stat-value">0</span>
</div>
<div class="stat-item">
<span class="stat-label">Errors:</span>
<span id="errorCount" class="stat-value">0</span>
</div>
</div>
</div>
<!-- Current Status -->
<div id="currentStatus" class="status-display">
<div class="status-item">
<span class="status-label">Current Task:</span>
<span id="currentTask" class="status-value">Ready to start</span>
</div>
<div class="status-item">
<span class="status-label">Current Track:</span>
<span id="currentTrack" class="status-value">-</span>
</div>
<div class="status-item">
<span class="status-label">ETA:</span>
<span id="eta" class="status-value">-</span>
</div>
</div>
<!-- Detailed Log -->
<div id="syncLog" class="sync-log" style="display: none;">
<h3><i class="fas fa-list"></i> Sync Log</h3>
<div id="logContent" class="log-content"></div>
</div>
<!-- Summary Report -->
<div id="syncSummary" class="sync-summary" style="display: none;">
<h3><i class="fas fa-chart-pie"></i> Sync Summary</h3>
<div id="summaryContent" class="summary-content"></div>
</div>
</div>
<div class="info-grid">
<div class="info-card">
<h3><i class="fas fa-cogs"></i> What This Sync Does</h3>
<ul>
<li><i class="fas fa-check text-success"></i> Extracts comprehensive metadata from existing callback data</li>
<li><i class="fas fa-check text-success"></i> Downloads all audio files locally to /audio_files/</li>
<li><i class="fas fa-check text-success"></i> Updates database with individual metadata columns</li>
<li><i class="fas fa-check text-success"></i> Preserves original data while adding new structure</li>
<li><i class="fas fa-check text-success"></i> Logs all download activities</li>
</ul>
</div>
<div class="info-card">
<h3><i class="fas fa-chart-bar"></i> Missing Data on Cards</h3>
<ul>
<li><i class="fas fa-music"></i> Audio Quality: Bitrate, Sample Rate, Format</li>
<li><i class="fas fa-clock"></i> Processing Time: How long generation took</li>
<li><i class="fas fa-dollar-sign"></i> Cost Info: API cost, credits used</li>
<li><i class="fas fa-sliders-h"></i> Generation Parameters: Model version, temperature</li>
<li><i class="fas fa-chart-line"></i> Audio Analysis: Loudness, dynamic range</li>
<li><i class="fas fa-list"></i> Audio Segments: Verse/chorus timestamps</li>
<li><i class="fas fa-wave-square"></i> Waveform Data: Visual waveform display</li>
<li><i class="fas fa-chart-area"></i> Spectrum Analysis: Frequency data</li>
</ul>
</div>
</div>
<div class="info-card">
<h3><i class="fas fa-info-circle"></i> Benefits of In-House Storage</h3>
<ul>
<li><i class="fas fa-shield-alt"></i> Complete Control: Full ownership of all files</li>
<li><i class="fas fa-rocket"></i> Faster Access: Local files serve instantly</li>
<li><i class="fas fa-lock"></i> Better Security: Files protected by your authentication</li>
<li><i class="fas fa-coins"></i> Cost Control: No external storage costs</li>
<li><i class="fas fa-wifi"></i> Offline Access: Works without internet</li>
<li><i class="fas fa-chart-pie"></i> Rich Analytics: Detailed metadata for insights</li>
</ul>
</div>
<div style="text-align: center;">
<a href="index.php" class="back-link">
<i class="fas fa-arrow-left"></i>
Back to Admin Dashboard
</a>
</div>
</div>
<script>
let syncRunning = false;
let syncInterval = null;
let startTime = null;
// Initialize the page
document.addEventListener('DOMContentLoaded', function() {
const startSyncBtn = document.getElementById('startSync');
const stopSyncBtn = document.getElementById('stopSync');
const refreshStatsBtn = document.getElementById('refreshStats');
startSyncBtn.addEventListener('click', startAsyncSync);
stopSyncBtn.addEventListener('click', stopAsyncSync);
refreshStatsBtn.addEventListener('click', refreshStats);
});
async function startAsyncSync() {
if (syncRunning) return;
syncRunning = true;
startTime = Date.now();
// Update UI
document.getElementById('startSync').style.display = 'none';
document.getElementById('stopSync').style.display = 'inline-flex';
document.getElementById('syncProgress').style.display = 'block';
document.getElementById('syncLog').style.display = 'block';
document.getElementById('syncSummary').style.display = 'none';
// Reset counters
document.getElementById('processedCount').textContent = '0';
document.getElementById('updatedCount').textContent = '0';
document.getElementById('filesDownloaded').textContent = '0';
document.getElementById('errorCount').textContent = '0';
document.getElementById('progressFill').style.width = '0%';
// Clear log
document.getElementById('logContent').innerHTML = '';
// Start the sync process
await performAsyncSync();
}
function stopAsyncSync() {
syncRunning = false;
if (syncInterval) {
clearInterval(syncInterval);
}
// Update UI
document.getElementById('startSync').style.display = 'inline-flex';
document.getElementById('stopSync').style.display = 'none';
document.getElementById('currentTask').textContent = 'Sync stopped by user';
document.getElementById('currentTrack').textContent = '-';
document.getElementById('eta').textContent = '-';
addLogEntry('Sync stopped by user', 'warning');
}
async function performAsyncSync() {
try {
addLogEntry('Starting comprehensive metadata sync...', 'info');
updateStatus('Initializing sync process', '-', '-');
// Get total tracks to process
const response = await fetch('async_sync_handler.php?action=get_tracks');
const data = await response.json();
if (!data.success) {
throw new Error(data.error || 'Failed to get tracks');
}
const totalTracks = data.total_tracks;
addLogEntry(`Found ${totalTracks} tracks to process`, 'info');
if (totalTracks === 0) {
addLogEntry('No tracks to sync', 'warning');
completeSync();
return;
}
// Process tracks in batches
let processed = 0;
let updated = 0;
let filesDownloaded = 0;
let errors = 0;
for (let i = 0; i < totalTracks && syncRunning; i += 5) { // Process 5 tracks at a time
const batch = await fetch('async_sync_handler.php?action=process_batch&offset=' + i + '&limit=5');
const batchData = await batch.json();
if (batchData.success) {
processed += batchData.processed;
updated += batchData.updated;
filesDownloaded += batchData.files_downloaded;
errors += batchData.errors;
// Update counters
document.getElementById('processedCount').textContent = processed;
document.getElementById('updatedCount').textContent = updated;
document.getElementById('filesDownloaded').textContent = filesDownloaded;
document.getElementById('errorCount').textContent = errors;
// Update progress
const progress = (processed / totalTracks) * 100;
document.getElementById('progressFill').style.width = progress + '%';
// Update status
const currentTrack = batchData.current_track || 'Processing batch ' + (i/5 + 1);
const eta = calculateETA(processed, totalTracks, startTime);
updateStatus('Processing tracks', currentTrack, eta);
// Add log entries
if (batchData.log_entries) {
batchData.log_entries.forEach(entry => {
addLogEntry(entry.message, entry.type);
});
}
// Small delay to prevent overwhelming the server
await new Promise(resolve => setTimeout(resolve, 100));
} else {
errors++;
document.getElementById('errorCount').textContent = errors;
addLogEntry('Batch processing error: ' + batchData.error, 'error');
}
}
if (syncRunning) {
completeSync(processed, updated, filesDownloaded, errors);
}
} catch (error) {
addLogEntry('Sync error: ' + error.message, 'error');
updateStatus('Error occurred', '-', '-');
syncRunning = false;
document.getElementById('startSync').style.display = 'inline-flex';
document.getElementById('stopSync').style.display = 'none';
}
}
function completeSync(processed = 0, updated = 0, filesDownloaded = 0, errors = 0) {
syncRunning = false;
document.getElementById('startSync').style.display = 'inline-flex';
document.getElementById('stopSync').style.display = 'none';
document.getElementById('currentTask').textContent = 'Sync completed';
document.getElementById('currentTrack').textContent = '-';
document.getElementById('eta').textContent = '-';
addLogEntry('Sync completed successfully!', 'success');
addLogEntry(`Processed: ${processed}, Updated: ${updated}, Files Downloaded: ${filesDownloaded}, Errors: ${errors}`, 'info');
// Show summary
showSummary(processed, updated, filesDownloaded, errors);
}
function showSummary(processed, updated, filesDownloaded, errors) {
const summaryContent = document.getElementById('summaryContent');
summaryContent.innerHTML = `
<div class="summary-item">
<h4>Sync Results</h4>
<p><strong>Total Processed:</strong> ${processed}</p>
<p><strong>Successfully Updated:</strong> ${updated}</p>
<p><strong>Files Downloaded:</strong> ${filesDownloaded}</p>
<p><strong>Errors:</strong> ${errors}</p>
<p><strong>Success Rate:</strong> ${processed > 0 ? Math.round((updated / processed) * 100) : 0}%</p>
</div>
<div class="summary-item">
<h4>What Was Synced</h4>
<ul>
<li>â
Comprehensive metadata extraction</li>
<li>â
Audio quality metrics</li>
<li>â
Generation parameters</li>
<li>â
Processing information</li>
<li>â
Cost information</li>
<li>â
Waveform data</li>
<li>â
Spectrum analysis</li>
<li>â
Audio segments</li>
<li>â
Local file downloads</li>
</ul>
</div>
`;
document.getElementById('syncSummary').style.display = 'block';
}
function updateStatus(task, track, eta) {
document.getElementById('currentTask').textContent = task;
document.getElementById('currentTrack').textContent = track;
document.getElementById('eta').textContent = eta;
}
function addLogEntry(message, type = 'info') {
const logContent = document.getElementById('logContent');
const timestamp = new Date().toLocaleTimeString();
const entry = document.createElement('div');
entry.className = `log-entry log-${type}`;
entry.innerHTML = `[${timestamp}] ${message}`;
logContent.appendChild(entry);
logContent.scrollTop = logContent.scrollHeight;
}
function calculateETA(processed, total, startTime) {
if (processed === 0) return 'Calculating...';
const elapsed = Date.now() - startTime;
const rate = processed / elapsed;
const remaining = total - processed;
const etaMs = remaining / rate;
if (etaMs < 60000) {
return Math.round(etaMs / 1000) + 's';
} else if (etaMs < 3600000) {
return Math.round(etaMs / 60000) + 'm';
} else {
return Math.round(etaMs / 3600000) + 'h';
}
}
async function refreshStats() {
try {
const response = await fetch('async_sync_handler.php?action=get_stats');
const data = await response.json();
if (data.success) {
// Update the stats cards
document.querySelectorAll('.stat-number').forEach((el, index) => {
const stats = [data.total_tracks, data.tracks_with_metadata, data.tracks_with_local_files, data.tracks_with_enhanced_metadata];
if (stats[index] !== undefined) {
el.textContent = stats[index].toLocaleString();
}
});
addLogEntry('Statistics refreshed', 'info');
}
} catch (error) {
addLogEntry('Failed to refresh stats: ' + error.message, 'error');
}
}
</script>
</body>
</html>