/**
* Music Metadata Analysis with Spotify and YouTube IDs
*
* This implementation extracts music data from Spotify and YouTube IDs,
* and presents it in a compelling UI using Elementor, JetEngine, and Element Pack Pro.
*/
// Step 1: Create the plugin structure
// Plugin Name: Music Metadata Analyzer
// Description: Extract and display music metadata from Spotify and YouTube
// Version: 1.0
// Author: Generated with Claude
if (!defined('ABSPATH')) exit; // Exit if accessed directly
class Music_Metadata_Analyzer {
/**
* Plugin initialization
*/
public function __construct() {
// Register activation hook
register_activation_hook(__FILE__, array($this, 'activate_plugin'));
// Add menu page
add_action('admin_menu', array($this, 'add_admin_menu'));
// Register settings
add_action('admin_init', array($this, 'register_settings'));
// Register custom post type
add_action('init', array($this, 'register_post_type'));
// Register shortcodes
add_shortcode('music_metadata', array($this, 'music_metadata_shortcode'));
add_shortcode('spotify_player', array($this, 'spotify_player_shortcode'));
add_shortcode('youtube_player', array($this, 'youtube_player_shortcode'));
// Add AJAX handlers for analysis
add_action('wp_ajax_analyze_track', array($this, 'ajax_analyze_track'));
add_action('wp_ajax_nopriv_analyze_track', array($this, 'ajax_analyze_track'));
// Enqueue scripts and styles
add_action('wp_enqueue_scripts', array($this, 'enqueue_scripts'));
}
/**
* Plugin activation
*/
public function activate_plugin() {
// Create necessary database tables
$this->create_database_tables();
// Flush rewrite rules for custom post types
flush_rewrite_rules();
}
/**
* Create database tables
*/
private function create_database_tables() {
global $wpdb;
$charset_collate = $wpdb->get_charset_collate();
// Table for caching API responses
$table_name = $wpdb->prefix . 'music_metadata_cache';
$sql = "CREATE TABLE $table_name (
id mediumint(9) NOT NULL AUTO_INCREMENT,
spotify_id varchar(255) DEFAULT '' NOT NULL,
youtube_id varchar(255) DEFAULT '' NOT NULL,
spotify_data longtext,
youtube_data longtext,
last_updated datetime DEFAULT CURRENT_TIMESTAMP NOT NULL,
PRIMARY KEY (id),
KEY spotify_id (spotify_id),
KEY youtube_id (youtube_id)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql);
}
/**
* Add admin menu
*/
public function add_admin_menu() {
add_menu_page(
'Music Metadata Analyzer',
'Music Analyzer',
'manage_options',
'music-metadata-analyzer',
array($this, 'admin_page'),
'dashicons-playlist-audio',
30
);
add_submenu_page(
'music-metadata-analyzer',
'Settings',
'Settings',
'manage_options',
'music-metadata-settings',
array($this, 'settings_page')
);
}
/**
* Register settings
*/
public function register_settings() {
register_setting('music_metadata_settings', 'spotify_client_id');
register_setting('music_metadata_settings', 'spotify_client_secret');
register_setting('music_metadata_settings', 'youtube_api_key');
register_setting('music_metadata_settings', 'cache_duration', array(
'default' => 86400, // 24 hours
'sanitize_callback' => 'absint',
));
}
/**
* Register custom post type
*/
public function register_post_type() {
$labels = array(
'name' => 'Music Tracks',
'singular_name' => 'Music Track',
'menu_name' => 'Music Tracks',
'add_new' => 'Add New Track',
'add_new_item' => 'Add New Music Track',
'edit_item' => 'Edit Music Track',
'new_item' => 'New Music Track',
'view_item' => 'View Music Track',
'search_items' => 'Search Music Tracks',
'not_found' => 'No music tracks found',
'not_found_in_trash' => 'No music tracks found in trash',
);
$args = array(
'labels' => $labels,
'public' => true,
'publicly_queryable' => true,
'show_ui' => true,
'show_in_menu' => true,
'query_var' => true,
'rewrite' => array('slug' => 'music-track'),
'capability_type' => 'post',
'has_archive' => true,
'hierarchical' => false,
'menu_position' => 5,
'menu_icon' => 'dashicons-playlist-audio',
'supports' => array('title', 'editor', 'thumbnail', 'excerpt', 'custom-fields'),
);
register_post_type('music_track', $args);
}
/**
* Admin page
*/
public function admin_page() {
?>
'',
'youtube_id' => '',
'display' => 'full', // full, basic, details
), $atts);
if (empty($atts['spotify_id']) && empty($atts['youtube_id'])) {
return '
.music-metadata-full, .music-metadata-basic, .music-metadata-details {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
color: #333;
max-width: 100%;
}
.track-header {
display: flex;
margin-bottom: 30px;
}
.track-image {
flex: 0 0 auto;
margin-right: 20px;
}
.track-image img {
width: 160px;
height: 160px;
object-fit: cover;
border-radius: 4px;
box-shadow: 0 4px 10px rgba(0,0,0,0.1);
}
.track-details {
flex: 1;
}
.track-title {
font-size: 24px;
font-weight: 700;
margin: 0 0 5px 0;
}
.track-artist {
font-size: 18px;
margin: 0 0 15px 0;
color: #555;
}
.track-album, .track-release, .track-duration {
margin: 5px 0;
font-size: 14px;
}
.track-popularity, .artist-popularity {
display: flex;
align-items: center;
margin: 10px 0;
}
.popularity-label, .genres-label, .followers-label, .stat-label, .feature-label {
font-weight: 600;
margin-right: 10px;
}
.popularity-bar, .feature-bar {
height: 8px;
background-color: #e0e0e0;
border-radius: 4px;
flex: 1;
margin: 0 10px;
overflow: hidden;
}
.popularity-progress, .feature-progress {
height: 100%;
background-color: #1DB954;
border-radius: 4px;
}
.audio-features-section, .artist-section, .youtube-section, .comments-section {
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #e0e0e0;
}
.audio-features-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 15px;
margin: 20px 0;
}
.feature-item {
margin-bottom: 10px;
}
.additional-features {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 15px;
margin-top: 20px;
}
.artist-details {
display: flex;
margin-top: 20px;
}
.artist-image img {
width: 120px;
height: 120px;
object-fit: cover;
border-radius: 50%;
margin-right: 20px;
}
.artist-name {
font-size: 20px;
margin: 0 0 10px 0;
}
.artist-genres, .artist-followers {
margin: 5px 0;
}
.youtube-stats {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 15px;
margin: 20px 0;
}
.stat-item {
background-color: #f5f5f5;
padding: 15px;
border-radius: 4px;
display: flex;
flex-direction: column;
}
.stat-value {
font-size: 18px;
font-weight: 600;
margin-top: 5px;
}
.video-description {
margin: 20px 0;
background-color: #f9f9f9;
padding: 15px;
border-radius: 4px;
}
.description-content {
white-space: pre-line;
font-size: 14px;
line-height: 1.5;
max-height: 300px;
overflow-y: auto;
}
.comments-list {
margin-top: 20px;
}
.comment-item {
margin-bottom: 20px;
padding-bottom: 20px;
border-bottom: 1px solid #e0e0e0;
}
.comment-author {
font-weight: 600;
margin-bottom: 5px;
}
.comment-text {
margin-bottom: 10px;
line-height: 1.5;
}
.comment-meta {
display: flex;
justify-content: space-between;
font-size: 12px;
color: #777;
}
@media (max-width: 768px) {
.track-header {
flex-direction: column;
}
.track-image {
margin-right: 0;
margin-bottom: 20px;
}
.audio-features-grid {
grid-template-columns: 1fr;
}
.additional-features {
grid-template-columns: 1fr;
}
.artist-details {
flex-direction: column;
}
.artist-image {
margin-bottom: 20px;
}
.youtube-stats {
grid-template-columns: 1fr;
}
}
';
return $html;
}
/**
* AJAX handler for track analysis
*/
public function ajax_analyze_track() {
// Check nonce
if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'music_metadata_nonce')) {
wp_send_json_error(array('message' => 'Invalid security token.'));
}
$spotify_id = isset($_POST['spotify_id']) ? sanitize_text_field($_POST['spotify_id']) : '';
$youtube_id = isset($_POST['youtube_id']) ? sanitize_text_field($_POST['youtube_id']) : '';
if (empty($spotify_id) && empty($youtube_id)) {
wp_send_json_error(array('message' => 'Please provide a Spotify ID or YouTube ID.'));
}
// Get track data
$track_data = $this->get_track_data($spotify_id, $youtube_id);
if (isset($track_data['error'])) {
wp_send_json_error(array('message' => $track_data['error']));
}
// Format the data for display
$formatted_data = $this->format_track_data($track_data);
// Render the HTML
$html = $this->render_track_data($formatted_data, 'full');
wp_send_json_success(array(
'html' => $html,
'data' => $formatted_data,
));
}
/**
* Enqueue scripts and styles
*/
public function enqueue_scripts() {
wp_enqueue_style('music-metadata-styles', plugin_dir_url(__FILE__) . 'css/music-metadata.css', array(), '1.0.0');
wp_enqueue_script('music-metadata-scripts', plugin_dir_url(__FILE__) . 'js/music-metadata.js', array('jquery'), '1.0.0', true);
wp_localize_script('music-metadata-scripts', 'music_metadata_vars', array(
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('music_metadata_nonce'),
));
}
}
// Initialize the plugin
$music_metadata_analyzer = new Music_Metadata_Analyzer();
Music Metadata Analyzer
Quick Analysis
Enter a Spotify ID or YouTube ID to analyze:
Analysis Results
Bulk Analysis
Upload a CSV file with Spotify IDs and YouTube IDs to analyze multiple tracks:
Music Metadata Analyzer Settings
Error: Please provide a Spotify ID or YouTube ID.
'; } // Get track data $track_data = $this->get_track_data($atts['spotify_id'], $atts['youtube_id']); if (isset($track_data['error'])) { return 'Error: ' . esc_html($track_data['error']) . '
'; } // Format the data for display $formatted_data = $this->format_track_data($track_data); // Return the formatted HTML return $this->render_track_data($formatted_data, $atts['display']); } /** * Spotify player shortcode */ public function spotify_player_shortcode($atts) { $atts = shortcode_atts(array( 'id' => '', 'width' => '100%', 'height' => '352', 'theme' => 'dark', ), $atts); if (empty($atts['id'])) { return 'Error: Please provide a Spotify ID.
'; } return ''; } /** * YouTube player shortcode */ public function youtube_player_shortcode($atts) { $atts = shortcode_atts(array( 'id' => '', 'width' => '100%', 'height' => '315', 'autoplay' => '0', ), $atts); if (empty($atts['id'])) { return 'Error: Please provide a YouTube ID.
'; } return ''; } /** * Get Spotify access token */ private function get_spotify_access_token() { $client_id = get_option('spotify_client_id'); $client_secret = get_option('spotify_client_secret'); if (empty($client_id) || empty($client_secret)) { return false; } // Check if we have a cached token $token = get_transient('spotify_access_token'); if ($token !== false) { return $token; } // Request a new token $args = array( 'headers' => array( 'Authorization' => 'Basic ' . base64_encode($client_id . ':' . $client_secret), 'Content-Type' => 'application/x-www-form-urlencoded', ), 'body' => array( 'grant_type' => 'client_credentials', ), ); $response = wp_remote_post('https://accounts.spotify.com/api/token', $args); if (is_wp_error($response)) { return false; } $body = json_decode(wp_remote_retrieve_body($response), true); if (isset($body['access_token'])) { $token = $body['access_token']; $expires_in = $body['expires_in']; // Usually 3600 seconds (1 hour) // Cache the token for a bit less than its expiry time set_transient('spotify_access_token', $token, $expires_in - 60); return $token; } return false; } /** * Get track data from APIs */ private function get_track_data($spotify_id, $youtube_id) { global $wpdb; $cache_duration = get_option('cache_duration', 86400); $table_name = $wpdb->prefix . 'music_metadata_cache'; // Check cache if enabled if ($cache_duration > 0) { $cache_query = $wpdb->prepare( "SELECT * FROM $table_name WHERE (spotify_id = %s OR youtube_id = %s) AND last_updated > DATE_SUB(NOW(), INTERVAL %d SECOND) LIMIT 1", $spotify_id, $youtube_id, $cache_duration ); $cached_data = $wpdb->get_row($cache_query); if ($cached_data) { return array( 'spotify' => json_decode($cached_data->spotify_data, true), 'youtube' => json_decode($cached_data->youtube_data, true), ); } } $track_data = array( 'spotify' => null, 'youtube' => null, ); // Fetch Spotify data if ID is provided if (!empty($spotify_id)) { $track_data['spotify'] = $this->get_spotify_track_data($spotify_id); } // Fetch YouTube data if ID is provided if (!empty($youtube_id)) { $track_data['youtube'] = $this->get_youtube_video_data($youtube_id); } // Store in cache if enabled if ($cache_duration > 0) { $wpdb->replace( $table_name, array( 'spotify_id' => $spotify_id, 'youtube_id' => $youtube_id, 'spotify_data' => json_encode($track_data['spotify']), 'youtube_data' => json_encode($track_data['youtube']), 'last_updated' => current_time('mysql'), ), array('%s', '%s', '%s', '%s', '%s') ); } return $track_data; } /** * Get Spotify track data */ private function get_spotify_track_data($spotify_id) { if (empty($spotify_id)) { return array('error' => 'No Spotify ID provided'); } $access_token = $this->get_spotify_access_token(); if (!$access_token) { return array('error' => 'Failed to get Spotify access token'); } // Get track data $track_args = array( 'headers' => array( 'Authorization' => 'Bearer ' . $access_token, ), ); $track_response = wp_remote_get("https://api.spotify.com/v1/tracks/{$spotify_id}", $track_args); if (is_wp_error($track_response)) { return array('error' => $track_response->get_error_message()); } $track_data = json_decode(wp_remote_retrieve_body($track_response), true); if (isset($track_data['error'])) { return array('error' => $track_data['error']['message']); } // Get audio features $features_response = wp_remote_get("https://api.spotify.com/v1/audio-features/{$spotify_id}", $track_args); if (!is_wp_error($features_response)) { $features_data = json_decode(wp_remote_retrieve_body($features_response), true); if (!isset($features_data['error'])) { $track_data['audio_features'] = $features_data; } } // Get artist data if (isset($track_data['artists'][0]['id'])) { $artist_id = $track_data['artists'][0]['id']; $artist_response = wp_remote_get("https://api.spotify.com/v1/artists/{$artist_id}", $track_args); if (!is_wp_error($artist_response)) { $artist_data = json_decode(wp_remote_retrieve_body($artist_response), true); if (!isset($artist_data['error'])) { $track_data['artist_details'] = $artist_data; } } } return $track_data; } /** * Get YouTube video data */ private function get_youtube_video_data($youtube_id) { if (empty($youtube_id)) { return array('error' => 'No YouTube ID provided'); } $api_key = get_option('youtube_api_key'); if (empty($api_key)) { return array('error' => 'YouTube API key not configured'); } // Get video data $video_url = add_query_arg( array( 'part' => 'snippet,contentDetails,statistics', 'id' => $youtube_id, 'key' => $api_key, ), 'https://www.googleapis.com/youtube/v3/videos' ); $video_response = wp_remote_get($video_url); if (is_wp_error($video_response)) { return array('error' => $video_response->get_error_message()); } $video_body = json_decode(wp_remote_retrieve_body($video_response), true); if (empty($video_body['items'])) { return array('error' => 'YouTube video not found'); } $video_data = $video_body['items'][0]; // Get comments $comments_url = add_query_arg( array( 'part' => 'snippet', 'videoId' => $youtube_id, 'maxResults' => 10, 'textFormat' => 'plainText', 'key' => $api_key, ), 'https://www.googleapis.com/youtube/v3/commentThreads' ); $comments_response = wp_remote_get($comments_url); if (!is_wp_error($comments_response)) { $comments_body = json_decode(wp_remote_retrieve_body($comments_response), true); if (!empty($comments_body['items'])) { $video_data['comments'] = $comments_body['items']; } } return $video_data; } /** * Format track data for display */ private function format_track_data($track_data) { $formatted_data = array( 'track_info' => array( 'title' => '', 'artist' => '', 'album' => '', 'release_date' => '', 'duration' => '', 'popularity' => '', 'image_url' => '', ), 'audio_features' => array(), 'artist_details' => array(), 'video_info' => array( 'title' => '', 'description' => '', 'published_at' => '', 'view_count' => '', 'like_count' => '', 'comment_count' => '', 'thumbnail_url' => '', ), 'comments' => array(), ); // Format Spotify data if (!empty($track_data['spotify']) && !isset($track_data['spotify']['error'])) { $spotify = $track_data['spotify']; $formatted_data['track_info']['title'] = $spotify['name'] ?? ''; if (isset($spotify['artists']) && is_array($spotify['artists'])) { $artists = array(); foreach ($spotify['artists'] as $artist) { $artists[] = $artist['name']; } $formatted_data['track_info']['artist'] = implode(', ', $artists); } $formatted_data['track_info']['album'] = $spotify['album']['name'] ?? ''; $formatted_data['track_info']['release_date'] = $spotify['album']['release_date'] ?? ''; if (isset($spotify['duration_ms'])) { $minutes = floor($spotify['duration_ms'] / 60000); $seconds = floor(($spotify['duration_ms'] % 60000) / 1000); $formatted_data['track_info']['duration'] = sprintf('%d:%02d', $minutes, $seconds); } $formatted_data['track_info']['popularity'] = $spotify['popularity'] ?? ''; if (isset($spotify['album']['images'][0]['url'])) { $formatted_data['track_info']['image_url'] = $spotify['album']['images'][0]['url']; } // Format audio features if (isset($spotify['audio_features']) && !isset($spotify['audio_features']['error'])) { $features = $spotify['audio_features']; $formatted_data['audio_features'] = array( 'danceability' => isset($features['danceability']) ? round($features['danceability'] * 100) : 0, 'energy' => isset($features['energy']) ? round($features['energy'] * 100) : 0, 'speechiness' => isset($features['speechiness']) ? round($features['speechiness'] * 100) : 0, 'acousticness' => isset($features['acousticness']) ? round($features['acousticness'] * 100) : 0, 'instrumentalness' => isset($features['instrumentalness']) ? round($features['instrumentalness'] * 100) : 0, 'liveness' => isset($features['liveness']) ? round($features['liveness'] * 100) : 0, 'valence' => isset($features['valence']) ? round($features['valence'] * 100) : 0, 'tempo' => $features['tempo'] ?? '', 'key' => $features['key'] ?? '', 'mode' => isset($features['mode']) ? ($features['mode'] == 1 ? 'Major' : 'Minor') : '', 'time_signature' => $features['time_signature'] ?? '', ); // Map key numbers to names if (isset($features['key']) && $features['key'] >= 0) { $key_names = array('C', 'C♯/D♭', 'D', 'D♯/E♭', 'E', 'F', 'F♯/G♭', 'G', 'G♯/A♭', 'A', 'A♯/B♭', 'B'); $formatted_data['audio_features']['key'] = $key_names[$features['key']]; } } // Format artist details if (isset($spotify['artist_details']) && !isset($spotify['artist_details']['error'])) { $artist = $spotify['artist_details']; $formatted_data['artist_details'] = array( 'name' => $artist['name'] ?? '', 'genres' => $artist['genres'] ?? array(), 'popularity' => $artist['popularity'] ?? '', 'followers' => isset($artist['followers']['total']) ? number_format($artist['followers']['total']) : '', 'image_url' => isset($artist['images'][0]['url']) ? $artist['images'][0]['url'] : '', ); } } // Format YouTube data if (!empty($track_data['youtube']) && !isset($track_data['youtube']['error'])) { $youtube = $track_data['youtube']; if (empty($formatted_data['track_info']['title']) && isset($youtube['snippet']['title'])) { $formatted_data['track_info']['title'] = $youtube['snippet']['title']; } if (empty($formatted_data['track_info']['artist']) && isset($youtube['snippet']['channelTitle'])) { $formatted_data['track_info']['artist'] = $youtube['snippet']['channelTitle']; } if (empty($formatted_data['track_info']['image_url']) && isset($youtube['snippet']['thumbnails']['high']['url'])) { $formatted_data['track_info']['image_url'] = $youtube['snippet']['thumbnails']['high']['url']; } $formatted_data['video_info']['title'] = $youtube['snippet']['title'] ?? ''; $formatted_data['video_info']['description'] = $youtube['snippet']['description'] ?? ''; $formatted_data['video_info']['published_at'] = isset($youtube['snippet']['publishedAt']) ? date('F j, Y', strtotime($youtube['snippet']['publishedAt'])) : ''; $formatted_data['video_info']['view_count'] = isset($youtube['statistics']['viewCount']) ? number_format($youtube['statistics']['viewCount']) : ''; $formatted_data['video_info']['like_count'] = isset($youtube['statistics']['likeCount']) ? number_format($youtube['statistics']['likeCount']) : ''; $formatted_data['video_info']['comment_count'] = isset($youtube['statistics']['commentCount']) ? number_format($youtube['statistics']['commentCount']) : ''; $formatted_data['video_info']['thumbnail_url'] = isset($youtube['snippet']['thumbnails']['high']['url']) ? $youtube['snippet']['thumbnails']['high']['url'] : ''; // Format duration if (isset($youtube['contentDetails']['duration'])) { $duration = new DateInterval($youtube['contentDetails']['duration']); $formatted_duration = ''; if ($duration->h > 0) { $formatted_duration .= $duration->h . ':'; } $formatted_duration .= sprintf('%02d:%02d', $duration->i, $duration->s); if (empty($formatted_data['track_info']['duration'])) { $formatted_data['track_info']['duration'] = $formatted_duration; } } // Format comments if (isset($youtube['comments']) && is_array($youtube['comments'])) { foreach ($youtube['comments'] as $comment) { if (isset($comment['snippet']['topLevelComment']['snippet'])) { $comment_data = $comment['snippet']['topLevelComment']['snippet']; $formatted_data['comments'][] = array( 'text' => $comment_data['textDisplay'] ?? '', 'author' => $comment_data['authorDisplayName'] ?? '', 'published_at' => isset($comment_data['publishedAt']) ? date('F j, Y', strtotime($comment_data['publishedAt'])) : '', 'like_count' => isset($comment_data['likeCount']) ? number_format($comment_data['likeCount']) : '0', ); } } } } return $formatted_data; } /** * Render track data as HTML */ private function render_track_data($data, $display_type = 'full') { ob_start(); if ($display_type === 'basic') { // Basic info only ?>Audio Features
$value): ?>
%
Tempo:
BPM
Key:
Time Signature:
No audio features available.
YouTube Statistics
Views:
Likes:
Comments:
Album:
Released:
Duration:
Popularity:
/100
Audio Features
$value): ?>
%
Tempo:
BPM
Key:
Time Signature:
Artist Details
Genres:
Popularity:
/100
Followers:
YouTube Statistics
Views:
Likes:
Comments:
Published:
Top Comments