您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Displays the BPM of a song on YouTube using Spotify API
// ==UserScript== // @name YouTube BPM Display // @namespace http://tampermonkey.net/ // @version 1.4 // @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://greasyfork.org/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")) { setTimeout(() => { displaySongInfo("Please provide your Spotify API keys to use this script."); }, 2000); 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 { setTimeout(() => { displaySongInfo("Invalid Spotify API keys. Please provide valid keys."); }, 2000); } } }); } // Function to detect if full screen mode is active function isFullScreen() { return !!(document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || document.msFullscreenElement); } // Function to move song-info based on full-screen state function handleFullScreenChange() { let songInfoElement = document.getElementById('song_info'); if (songInfoElement) { if (isFullScreen()) { const fullScreenContainer = document.querySelector('#player-full-bleed-container'); if (fullScreenContainer) { songInfoElement.remove(); fullScreenContainer.appendChild(songInfoElement); } } else { const playerElement = document.querySelector('#player'); if (playerElement) { songInfoElement.remove(); playerElement.appendChild(songInfoElement); } } } } // 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 { displaySongInfo("No track found."); } } else { displaySongInfo("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 { displaySongInfo("Error fetching track features."); } } }); } // 5. Function to display BPM and track information function displaySongInfo(message) { 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'; songInfoElement.innerText = message; const playerElement = document.querySelector('#player'); if (playerElement) { playerElement.appendChild(songInfoElement); } else { console.error("Could not find the player element to add BPM."); } handleFullScreenChange(); } // 6. Function to update BPM and track information function updateSongInfo(bpm, track) { let existingElement = document.getElementById('song_info'); if (existingElement) { existingElement.innerText = ''; } else { displaySongInfo(''); existingElement = document.getElementById('song_info'); } let bpmElement = document.createElement('div'); bpmElement.id = 'bpm'; bpmElement.style.fontSize = '60px'; bpmElement.innerText = `BPM: ${bpm ? bpm.toFixed(2) : 'Not available'}`; let artistElement = document.createElement('div'); artistElement.id = 'artist'; artistElement.style.fontSize = '20px'; artistElement.innerText = `Artist: ${track.artists[0].name}`; let songElement = document.createElement('div'); songElement.id = 'song'; songElement.style.fontSize = '20px'; songElement.innerText = `Song: ${track.name}`; existingElement.appendChild(bpmElement); existingElement.appendChild(artistElement); existingElement.appendChild(songElement); } // 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) { updateSongInfo(bpm, track); }); } else { displaySongInfo("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) { main(); } }, 1000); } // 9. Observe changes in URL and full screen mode function observeUrlChanges() { const urlObserver = new MutationObserver(() => { if (location.href !== lastUrl) { lastUrl = location.href; waitForTitle(); } }); urlObserver.observe(document.body, { childList: true, subtree: true }); } // 10. Observe fullscreen changes function observeFullScreenChanges() { document.addEventListener('fullscreenchange', handleFullScreenChange); } // 11. Load script window.addEventListener('load', () => { getAccessToken(function() { observeUrlChanges(); observeFullScreenChanges(); waitForTitle(); }); }); })();