YouTube BPM Display

Displays the BPM of a song on YouTube using Spotify API

目前为 2024-10-05 提交的版本。查看 最新版本

// ==UserScript==
// @name         YouTube BPM Display
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  Displays the BPM of a song on YouTube using Spotify API
// @author       Sergi0
// @match        https://www.youtube.com/*
// @grant        GM_xmlhttpRequest
// @connect      api.spotify.com
// @icon         https://www.freeiconspng.com/uploads/youtube-icon-app-logo-png-9.png
// @license      MIT
// @homepageURL  https://gf.qytechs.cn/es/scripts/511311
// ==/UserScript==

(function() {
    'use strict';

    // 1. Spotify API credentials
    const CLIENT_ID = 'YOUR_SPOTIFY_CLIENT_ID'; // Replace with your Client ID
    const CLIENT_SECRET = 'YOUR_SPOTIFY_CLIENT_SECRET'; // Replace with your Client Secret
    let accessToken = '';
    let lastTitle = '';
    let lastUrl = '';

    // 2. Function to get Spotify access token
    function getAccessToken(callback) {
        if (CLIENT_ID.includes("YOUR_SPOTIFY") || CLIENT_SECRET.includes("YOUR_SPOTIFY")) {
            displayMessage("Please provide your Spotify API keys to use this script.");
            return;
        }

        const authString = btoa(`${CLIENT_ID}:${CLIENT_SECRET}`);
        GM_xmlhttpRequest({
            method: 'POST',
            url: 'https://accounts.spotify.com/api/token',
            headers: {
                'Authorization': `Basic ${authString}`,
                'Content-Type': 'application/x-www-form-urlencoded'
            },
            data: 'grant_type=client_credentials',
            onload: function(response) {
                if (response.status === 200) {
                    const data = JSON.parse(response.responseText);
                    accessToken = data.access_token;
                    callback();
                } else {
                    displayMessage("Invalid Spotify API keys. Please provide valid keys.");
                }
            }
        });
    }

    // 3. Function to search track on Spotify
    function searchTrack(trackName, callback) {
        const query = encodeURIComponent(trackName);
        const apiUrl = `https://api.spotify.com/v1/search?q=${query}&type=track&limit=1`;

        GM_xmlhttpRequest({
            method: 'GET',
            url: apiUrl,
            headers: {
                'Authorization': `Bearer ${accessToken}`
            },
            onload: function(response) {
                if (response.status === 200) {
                    const data = JSON.parse(response.responseText);
                    if (data.tracks.items.length > 0) {
                        const track = data.tracks.items[0];
                        callback(track);
                    } else {
                        displayMessage("No track found.");
                    }
                } else {
                    displayMessage("Error in search request.");
                }
            }
        });
    }

    // 4. Function to get track features (including BPM)
    function getTrackFeatures(trackId, callback) {
        const apiUrl = `https://api.spotify.com/v1/audio-features/${trackId}`;

        GM_xmlhttpRequest({
            method: 'GET',
            url: apiUrl,
            headers: {
                'Authorization': `Bearer ${accessToken}`
            },
            onload: function(response) {
                if (response.status === 200) {
                    const data = JSON.parse(response.responseText);
                    callback(data.tempo);
                } else {
                    displayMessage("Error fetching track features.");
                }
            }
        });
    }

    // 5. Function to display BPM and track information
    function displaySongInfo(bpm, track) {
        let existingElement = document.getElementById('song_info');
        if (existingElement) {
            existingElement.remove();
        }

        let songInfoElement = document.createElement('div');
        songInfoElement.id = 'song_info';
        songInfoElement.style.position = 'absolute';
        songInfoElement.style.top = '10px';
        songInfoElement.style.right = '10px';
        songInfoElement.style.backgroundColor = '#ff0000';
        songInfoElement.style.color = 'white';
        songInfoElement.style.padding = '15px';
        songInfoElement.style.borderRadius = '5px';
        songInfoElement.style.fontWeight = 'bold';
        songInfoElement.style.fontSize = '20px';
        songInfoElement.style.textAlign = 'center';

        let bpmElement = document.createElement('div');
        bpmElement.id = 'bpm';
        bpmElement.style.fontSize = '60px';
        bpmElement.innerText = `BPM: ${bpm ? bpm.toFixed(2) : 'Not available'}`;

        let artistSongElement = document.createElement('div');
        artistSongElement.id = 'artist_song';
        artistSongElement.style.fontSize = '20px';
        artistSongElement.innerText = `${track.artists[0].name} / ${track.name}`;

        songInfoElement.appendChild(bpmElement);
        songInfoElement.appendChild(artistSongElement);

        const playerElement = document.querySelector('#player');
        if (playerElement) {
            playerElement.appendChild(songInfoElement);
        } else {
            displayMessage("Could not find the player element to add BPM.");
        }
    }

    // 6. Function to display messages
    function displayMessage(message) {
        let existingElement = document.getElementById('message_display');
        if (existingElement) {
            existingElement.remove();
        }

        let messageElement = document.createElement('div');
        messageElement.id = 'message_display';
        messageElement.style.position = 'fixed';
        messageElement.style.top = '0';
        messageElement.style.left = '0';
        messageElement.style.width = '100%';
        messageElement.style.backgroundColor = '#ff0000';
        messageElement.style.color = 'white';
        messageElement.style.padding = '10px';
        messageElement.style.fontWeight = 'bold';
        messageElement.style.fontSize = '20px';
        messageElement.style.textAlign = 'center';
        messageElement.style.zIndex = '9999';
        messageElement.innerText = message;

        document.body.appendChild(messageElement);
    }

    // 7. Main function
    function main() {
        const videoTitleElement = document.querySelector('#title h1');
        if (videoTitleElement) {
            const titleText = videoTitleElement.innerText.trim();
            if (titleText !== lastTitle) {
                lastTitle = titleText;
                searchTrack(titleText, function(track) {
                    if (track) {
                        getTrackFeatures(track.id, function(bpm) {
                            displaySongInfo(bpm, track);
                        });
                    } else {
                        displayMessage("No track found.");
                    }
                });
            }
        }
    }

    // 8. Function to wait for the video title indefinitely
    function waitForTitle() {
        const interval = setInterval(() => {
            const videoTitleElement = document.querySelector('#title h1');
            if (videoTitleElement) {
                clearInterval(interval);
                main();
            }
        }, 1000);
    }

    // 9. Observe changes in URL
    function observeUrlChanges() {
        const urlObserver = new MutationObserver(() => {
            if (location.href !== lastUrl) {
                lastUrl = location.href;
                waitForTitle();
            }
        });

        urlObserver.observe(document.body, { childList: true, subtree: true });
    }

    // 10. Observe changes in title
    function observeTitleChanges() {
        const titleElement = document.querySelector('#title');
        if (titleElement) {
            const titleObserver = new MutationObserver(main);
            titleObserver.observe(titleElement, {
                childList: true,
                subtree: true
            });
        }
    }

    // 11. Load script
    window.addEventListener('load', () => {
        getAccessToken(function() {
            observeUrlChanges();
            observeTitleChanges();
            waitForTitle();
        });
    });

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址