您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Display Waveforms for Tracks on Spotify
// ==UserScript== // @name Spotify Waveform // @namespace https://gf.qytechs.cn/en/users/943407-webchantment // @version 1.2 // @description Display Waveforms for Tracks on Spotify // @author Webchantment // @match https://open.spotify.com/* // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_addStyle // ==/UserScript== (async () => { //*********************SETTINGS***************************// const clientID = "myClientID"; const clientSecret = "myClientSecret"; let height = 36; let color = "lightgrey"; //********************************************************// let playObserver; let nowPlayingLink; let progressDiv; let firstLoad = true; let adObserver; let adDeteted = false; const nowPlayingDiv = "footer > div > div > div"; init(); function init() { playObserver = new MutationObserver(() => { if (firstLoad) { nowPlayingLink = document.querySelector(nowPlayingDiv + " > div > div > a"); progressDiv = document.querySelector(nowPlayingDiv + " > div.playback-bar > div > div"); } if (nowPlayingLink && progressDiv) { playObserver.disconnect(); firstLoad = false; //check for API key if (clientID === "myClientID" || clientSecret === "myClientSecret") { alert("Spotify Waveform: Please enter clientID and clientSecret"); return; } progressDiv.style.backgroundImage = ""; console.log("loading waveform..."); loadWaveform(); playObserver.observe(nowPlayingLink, { attributeFilter: ["href"] }); if (!adObserver) { adObserver = new MutationObserver(() => { const adDiv = document.querySelector(nowPlayingDiv + "[aria-label='Advertisement']"); if (adDiv && !adDeteted) { adDeteted = true; console.log("ads started."); playObserver.disconnect(); firstLoad = true; progressDiv.style.backgroundImage = ""; } else if (!adDiv && adDeteted) { adDeteted = false; console.log("ads finished."); init(); } }); adObserver.observe(document.querySelector(nowPlayingDiv), { attributeFilter: ["aria-label"] }); } } }); playObserver.observe(document.body, { childList: true, subtree: true }); } async function loadWaveform() { const trackId = getTrackId(); const spotifyTrackAnalysis = await getSpotifyTrackAnalysis(await getSpotifyToken(), trackId); const waveform = createWaveform(spotifyTrackAnalysis); drawWaveform(waveform); } function getTrackId() { let nowPlayingURL = nowPlayingLink.href; let trackId = nowPlayingURL.substring(nowPlayingURL.lastIndexOf("%3A") + 3, nowPlayingURL.length); return trackId; } async function getSpotifyToken() { let token = await GM_getValue("spotifyToken"); if (token && (Date.now() - token.date < 3600000)) //lasts for 1 hour return token.value; let myHeaders = new Headers(); myHeaders.append("Authorization", "Basic " + btoa(clientID + ":" + clientSecret)); myHeaders.append("Content-Type", "application/x-www-form-urlencoded"); var urlencoded = new URLSearchParams(); urlencoded.append("grant_type", "client_credentials"); const requestOptions = { method: 'POST', headers: myHeaders, body: urlencoded, redirect: 'follow' } let res = await fetch("https://accounts.spotify.com/api/token", requestOptions); res = await res.json(); //save token to storage GM_setValue("spotifyToken", { value: res.access_token, date: Date.now() }); //console.log(res); return res.access_token; } async function getSpotifyTrackAnalysis(spotifyToken, trackId) { const trackAnalysisURL = `https://api.spotify.com/v1/audio-analysis/${trackId}`; let myHeaders = new Headers(); myHeaders.append("Authorization", `Bearer ${spotifyToken}`); const requestOptions = { method: 'GET', headers: myHeaders } let res = await fetch(trackAnalysisURL, requestOptions); if (res.status === 500) { console.log("Error fetching spotify track analysis, retrying..."); res = await fetch(trackAnalysisURL, requestOptions); } res = await res.json(); //console.log(res); return res; } function createWaveform(data) { let duration = data.track.duration; let segments = data.segments.map(segment => { return { start: segment.start / duration, duration: segment.duration / duration, loudness: 1 - (Math.min(Math.max(segment.loudness_max, -17), 0) / -17) }; }); let min = Math.min(...segments.map(s => s.loudness)); let max = Math.max(...segments.map(s => s.loudness)); let levels = []; for (let i = 0.000; i < 1; i += 0.001) { let s = segments.find(segment => { return i <= segment.start + segment.duration; }); let loudness = Math.round((s.loudness / max) * 100) / 100; levels.push(loudness); } //console.log(levels); return levels; } function drawWaveform(waveform) { let canvas = document.createElement("canvas"); let width = progressDiv.clientWidth; canvas.width = width; canvas.height = height; let context = canvas.getContext("2d"); for (let x = 0; x < width; x++) { let i = Math.ceil(waveform.length * (x / width)); let h = Math.round(waveform[i] * height) / 2; context.fillStyle = color; context.fillRect(x, (height / 2) - h, 1, h); context.fillRect(x, (height / 2), 1, h); } progressDiv.style.height = height + "px"; progressDiv.style["margin-top"] = "-12px"; progressDiv.style.backgroundImage = "url(" + canvas.toDataURL() + ")"; } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址