/** * 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() { ?>

Music Metadata Analyzer

Quick Analysis

Enter a Spotify ID or YouTube ID to analyze:

The Spotify Track ID is the alphanumeric code found in a Spotify track URL.

The YouTube Video ID is the code found in a YouTube video URL after "?v=".

Bulk Analysis

Upload a CSV file with Spotify IDs and YouTube IDs to analyze multiple tracks:

CSV format: track_name,artist_name,spotify_id,youtube_id

Music Metadata Analyzer Settings

Spotify Client ID

Enter your Spotify Client ID from the Spotify Developer Dashboard.

Spotify Client Secret

Enter your Spotify Client Secret.

YouTube API Key

Enter your YouTube Data API key from the Google Cloud Console.

Cache Duration (seconds)

How long to cache API responses (in seconds). Set to 0 to disable caching.

'', 'youtube_id' => '', 'display' => 'full', // full, basic, details ), $atts); if (empty($atts['spotify_id']) && empty($atts['youtube_id'])) { return '

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 ?> .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();