YouTube BPM Display

Displays the BPM of a song on YouTube using the Spotify API

As of 03.10.2024. See апошняя версія.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         YouTube BPM Display
// @version      1.0 
// @description  Displays the BPM of a song on YouTube using the 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://greasyfork.org/es/scripts/511311

// @namespace http://violentmonkey.net/
// ==/UserScript==

(function() {
    'use strict';

    // 1. Spotify API credentials
    const CLIENT_ID = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
    const CLIENT_SECRET = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
    let accessToken = '';
    let lastTitle = '';
    let lastUrl = '';

    // 2. Function to get the access token from Spotify
    function getAccessToken(callback) {
        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) {
                    accessToken = JSON.parse(response.responseText).access_token;
                    callback();
                }
            }
        });
    }

    // 3. Function to search for the 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.id, track.artists[0].name, track.name);
                    } else {
                        callback(null, null, null);
                    }
                } else {
                    callback(null, null, null);
                }
            }
        });
    }

    // 4. Function to get audio 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 {
                    callback(null);
                }
            }
        });
    }

    // 5. Function to display BPM and song information
    function displayBPM(bpm, artist, title) {
        let existingSongInfoElement = document.getElementById('song_info');
        if (existingSongInfoElement) {
            existingSongInfoElement.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.zIndex = '9999';
        songInfoElement.style.borderRadius = '5px';
        songInfoElement.style.fontSize = '60px';
        songInfoElement.style.fontWeight = 'bold';
        songInfoElement.style.textAlign = 'center';

        let bpmElement = document.createElement('div');
        bpmElement.id = 'bpm';
        bpmElement.innerText = `BPM: ${bpm ? bpm.toFixed(2) : 'Unavailable'}`;

        let artistSongElement = document.createElement('div');
        artistSongElement.id = 'artist_song';
        artistSongElement.style.fontSize = '24px';
        artistSongElement.innerText = `${artist ? artist : 'Artist unavailable'} / ${title ? title : 'Title unavailable'}`;

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

        const playerElement = document.querySelector('#player');
        if (playerElement) {
            playerElement.appendChild(songInfoElement);
        }
    }

    // 6. 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(trackId, artist, title) {
                    if (trackId) {
                        getTrackFeatures(trackId, function(bpm) {
                            displayBPM(bpm, artist, title);
                        });
                    } else {
                        displayBPM(null, null, null);
                    }
                });
            }
        }
    }

    // 7. Function to wait for the title indefinitely
    function waitForTitle() {
        const interval = setInterval(() => {
            const videoTitleElement = document.querySelector('#title h1');
            if (videoTitleElement) {
                clearInterval(interval);
                main();
                setInterval(main, 2000); // Check every 2 seconds
            }
        }, 1000); // Check every second
    }

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

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

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

})();