返回首頁 

Youtube Genius Lyrics

Shows lyrics/songtexts from genius.com on Youtube next to music videos


Install this script?
Author's suggested script

You may also like Youtube Music Genius Lyrics.


Install this script
// ==UserScript==// @name            Youtube Genius Lyrics// @namespace       https://greasyfork.org/users/20068// @description     Shows lyrics/songtexts from genius.com on Youtube next to music videos// @description:es  Mostra la letra de genius.com de las canciones en Youtube junto a los vídeos musicales// @description:de  Zeigt den Songtext von genius.com neben Musikvideos auf Youtube// @description:fr  Présente les paroles des chansons de genius.com sur Youtube à côté des vidéos de musique.// @description:pl  Pokazuje teksty piosenek z genius.com na Youtube obok teledysków// @description:pt  Mostra letras de canções de genius.com no Youtube ao lado de vídeos de música// @description:it  Mostra i testi delle canzoni di genius.com su Youtube accanto ai video musicali// @description:ja  Youtube(ユーチューブ)では、ミュージックビデオの横に genius.com の歌詞が表示されます// @license         GPL-3.0-or-later; http://www.gnu.org/licenses/gpl-3.0.txt// @copyright       2020, cuzi (https://github.com/cvzi)// @author          cuzi// @icon            https://raw.githubusercontent.com/hfg-gmuend/openmoji/master/color/72x72/E044.png// @supportURL      https://github.com/cvzi/Youtube-Genius-Lyrics-userscript/issues// @version         10.10.12// @require         https://update.greasyfork.org/scripts/406698/GeniusLyrics.js// @require         https://cdnjs.cloudflare.com/ajax/libs/lz-string/1.5.0/lz-string.min.js// @grant           GM.xmlHttpRequest// @grant           GM.setValue// @grant           GM.getValue// @grant           GM.registerMenuCommand// @grant           GM_addValueChangeListener// @connect         genius.com// @match           https://www.youtube.com/*// @match           https://music.youtube.com/*// @exclude         https://www.youtube.com/embed/*// @exclude         https://www.youtube-nocookie.com/embed/*// @exclude         https://www.youtube.com/live_chat*// @exclude         https://www.youtube.com/live_chat_replay*// @exclude         /^https?://\S+\.(png|jpg|jpeg|gif|xml|svg|manifest|log|ini)[^\/]*$/// ==/UserScript==/*Copyright (C) 2020 cuzi ([email protected])This program is free software: you can redistribute it and/or modifyit under the terms of the GNU General Public License as published bythe Free Software Foundation, either version 3 of the License, or(at your option) any later version.This program is distributed in the hope that it will be useful,but WITHOUT ANY WARRANTY; without even the implied warranty ofMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See theGNU General Public License for more details.You should have received a copy of the GNU General Public Licensealong with this program.  If not, see <https://www.gnu.org/licenses/>.*//* global GM, genius, geniusLyrics, top, GM_addValueChangeListener, sessionStorage */ // eslint-disable-line no-unused-vars, no-redeclare/* jshint asi: true, esversion: 8 */'use strict'let geniusconst SCRIPT_NAME = 'Youtube Genius Lyrics'/** @type {globalThis.PromiseConstructor} */const Promise = (async () => { })().constructor // YouTube polyfill to Promise in older browsers will make the feature being unstable.let lyricsDisplayState = 'hidden'let disableShowLyricsButton = false // hide if the page is confirmed as non-video pagelet isYouTubeLive = falselet iframeBlankURL = nullconst EXCLUDE_LIVE_VIDEO = true // this should be configurable in "options" ? [>=25min]const EXCLUDE_SHORT_LEN = true // this should be configurable in "options" ? [<15s]const elmBuild = (tag, ...contents) => {/** @type {HTMLElement} */const elm = typeof tag === 'string' ? document.createElement(tag) : tagfor (const content of contents) {if (!content || typeof content !== 'object' || (content instanceof Node)) { // eslint-disable-line no-undefelm.append(content)} else if (content.length > 0) {elm.appendChild(elmBuild(...content))} else if (content.style) {Object.assign(elm.style, content.style)} else if (content.classList) {elm.classList.add(...content.classList)} else if (content.attr) {for (const [attr, val] of Object.entries(content.attr)) elm.setAttribute(attr, val)} else {Object.assign(elm, content)}}return elm}const elmBuildNS = (tag, ...contents) => {const ns = 'http://www.w3.org/2000/svg'/** @type {Element} */const elm = typeof tag === 'string' ? document.createElementNS(ns, tag) : tagfor (const content of contents) {if (!content || typeof content !== 'object' || (content instanceof Node)) { // eslint-disable-line no-undefelm.append(content)} else if (content.length > 0) {elm.appendChild(elmBuildNS(...content))} else if (content.style) {Object.assign(elm.style, content.style)} else if (content.classList) {elm.classList.add(...content.classList)} else if (content.attr) {for (const [attr, val] of Object.entries(content.attr)) elm.setAttributeNS(null, attr, val)} else if (content.attrNS) {for (const [attr, val] of Object.entries(content.attrNS)) elm.setAttributeNS(ns, attr, val)} else {Object.assign(elm, content)}}return elm}function addCss () {let style = document.querySelector('style#youtube_genius_lyrics_style')if (style === null) {style = document.createElement('style')style.id = 'youtube_genius_lyrics_style'style.textContent = `body #mycaptchahint897454 {z-index: 2070;}body #myoverlay7658438 {z-index: 2050;}body #myconfigwin39457845 {z-index: 2060;}html {/* allow modification from external */--ygl-container-right: var(--ytd-margin-6x, 4px);--ygl-container-default-padding: 2px 6px;--ygl-spinner-color: rgb(255, 255, 100);}#lyricscontainer {box-sizing: border-box;position: fixed;right: 0;margin: 0px;padding: 0px;background-color: white;z-index: 2001;font-size: 1.4rem;border: 0;border-radius: 0;background: var(--ytd-searchbox-background);color: var(--ytd-searchbox-text-color);border: 1px solid var(--ytd-searchbox-legacy-border-color);padding: var(--ygl-container-default-padding);/* overrided by found/loading */line-height: 100%;width: calc(var(--ygl-container-width, 324px) - var(--ytd-margin-6x, 24px) + 7px);right: calc(var(--ygl-container-right) - 1px);}#lyricsiframe {opacity: 0.1;transition: opacity 2s;margin: 0;padding: 0;}.lyricsnavbar {font-size: 0.83em;text-align: right;padding-right: 10px;background-color: #fafafa;background: transparent;color: inherit;display: flex;user-select: none;flex-direction: row;justify-content: end;align-items: center;align-items: stretch;}.lyricsnavbar span {color: var(--yt-live-chat-primary-text-color);text-decoration: none;transition: color 400ms;}.lyricsnavbar span:hover {color: var(--yt-live-chat-toast-action-color);text-decoration: none;}body .loadingspinner {/* override .loadingspinner */color: currentColor;font-size: 1em;line-height: 2.5em;--ygl-spinner-border-color: var(--yt-live-chat-secondary-text-color, #181818);border-color: var(--ygl-spinner-color) var(--ygl-spinner-border-color) var(--ygl-spinner-border-color) var(--ygl-spinner-border-color);}.loadingspinnerholder {z-index: 2050;background-color: inherit;cursor: progress;}.lorem br {height: 0px;line-height: 0;font-size: 0;padding: 0;margin: 0;}.lorem {--ygl-lorem-gray: var(--yt-live-chat-disabled-icon-button-color);padding: 10px 0px 0px 15px;font-size: 1.4rem;line-height: 2.2rem;letter-spacing: 0.3rem;user-select: none !important;pointer-events: none !important;width: 100%;}/* this is only supported in modern browsers */@property --num {syntax: '<integer>';initial-value: 0;inherits: false;}@keyframes loadingLorem {0% {--num: 0;}99% {--num: 8;}100% {--num: 0;}}.lorem-scroll {--num: 0;animation: loadingLorem 4s ease infinite;margin-top: calc(-13rem - 2rem*var(--num, 0));contain: content;width: 96%;overflow: hidden;}.lorem .white {background-color: inherit;color: white;user-select: none !important;}.lorem .gray {background-color: var(--ygl-lorem-gray);color: transparent;user-select: none !important;}#showlyricsbutton {position: fixed; /* youtube's unknown layout bug - the 'absolute' position top would offset upwards by around 80px after the page is changed *//* note: youtube layouts are inside ytd-watch-flexy by default; therefore the 'absolute' layout for elements outside ytd-watch-flexy is not guaranteed */z-index: 3000;right: 4px;cursor: pointer;border-radius: 50%;margin: auto;padding: 0px 1px;text-align: center;font-size: 15px;line-height: 14px;background: #ffff64;color: #000a;padding: 9px;display: flex;align-content: center;align-items: center;justify-content: center;contain: strict;opacity: 0.7;transition: opacity 50ms;/*--ytd-masthead-height--ytd-toolbar-height--ytd-watch-flexy-masthead-height*/top: var(--ytd-toolbar-height, var(--ytd-masthead-height, var(--ytd-watch-flexy-masthead-height, 56px)));}#showlyricsbutton.hide-during-ytlive {display: none;}#showlyricsbutton:hover {opacity: 1.0;}/* :fullscreen shall be sufficient for modern browsers *//* see more at https://caniuse.com/mdn-css_selectors_fullscreen *//* just except opera ? */:fullscreen #showlyricsbutton, :fullscreen #lyricscontainer {display: none;}:-moz-full-screen #showlyricsbutton {display: none;}:-webkit-full-screen #showlyricsbutton {display: none;}#showlyricsbutton::before {color: inherit;content: 'G';position: absolute;display: block;pointer-events: none;user-select: none;touch-action: none;}.youtube-genius-lyrics-search-container {border: 1px solid black;border-radius: 3px;--ygl-lyricscontainer-padding: 0px 10px;}span.youtube-genius-lyrics-r###lts-line-separator,span.youtube-genius-lyrics-found-separator {padding: 0px 3px;}.youtube-genius-lyrics-r###lts-container {border: 1px solid black;border-radius: 3px;}ol.youtube-genius-lyrics-r###lts-tracklist {list-style: none;width: 99%;font-size: 1.15em;}li.youtube-genius-lyrics-r###lts-li {cursor: pointer;transition: background-color 0.2s;margin: 2px;border-radius: 3px;padding: 8px 6px;}span.youtube-genius-lyrics-r###lts-hide-btn,span.youtube-genius-lyrics-r###lts-back-btn,span.youtube-genius-lyrics-found-hide-btn,span.youtube-genius-lyrics-found-back-btn {cursor: pointer;}li.youtube-genius-lyrics-r###lts-li div.onhover {display: none;margin-top: -0.25em;}li.youtube-genius-lyrics-r###lts-li div.onout {display: block;}li.youtube-genius-lyrics-r###lts-li:hover div.onhover {display: block;}li.youtube-genius-lyrics-r###lts-li:hover div.onout {display: none;}ol.youtube-genius-lyrics-r###lts-tracklist {font-size: var(--ytd-link-font-size);}li.youtube-genius-lyrics-r###lts-li {/* --tyt-tracklist-li-background: var(--yt-live-chat-slider-container-color); */--tyt-tracklist-li-background: var(--ytd-searchbox-legacy-button-color);background-color: var(--tyt-tracklist-li-background);color: var(--ytd-searchbox-text-color);border: 1px solid var(--ytd-searchbox-legacy-border-color);list-style: none;/*color: var(--yt-live-chat-secondary-text-color);--tyt-tracklist-li-background: var(--ytd-searchbox-legacy-button-color);--tyt-tracklist-li-background: var(--yt-spec-commerce-tonal-hover);*/opacity: 0.9;}li.youtube-genius-lyrics-r###lts-li:hover {--tyt-tracklist-li-background: var(--yt-live-chat-button-dark-background-color);/* --tyt-tracklist-li-background: var(--yt-live-chat-slider-active-color); */border: 1px solid var(--ytd-searchbox-legacy-button-hover-border-color);opacity: 1.0;}li.youtube-genius-lyrics-r###lts-li.lyrics-minor-r###lt {font-size: 80%;opacity: 0.6;}li.youtube-genius-lyrics-r###lts-li.lyrics-minor-r###lt:hover {opacity: 0.7;}li.youtube-genius-lyrics-r###lts-li * {pointer-events: none;}li.youtube-genius-lyrics-r###lts-li div.onhover span {color: black;font-size: 2.0em;}li.youtube-genius-lyrics-r###lts-li div.onout span {font-size: 1.5em;}body #lyricscontainer ol.tracklist li .onhover,body #lyricscontainer ol.tracklist li .onout {display: none;}body #lyricscontainer>ol.tracklist {max-width: 480px;min-width: 280px;width: auto;}span.youtube-genius-lyrics-found-config-btn {cursor: pointer;}span.youtube-genius-lyrics-found-wonglyrics-btn {cursor: pointer;}html {--ygl-theater-player-max-width: '--NULL--';--ygl-theater-player-float: '--NULL--';}html[youtube-genius-lyrics-container="found"] ytd-watch-flexy[theater],html[youtube-genius-lyrics-container="loading"] ytd-watch-flexy[theater] {--ygl-theater-player-max-width: 50%;--ygl-theater-player-float: right;}ytd-watch-flexy[theater] #movie_player .ytp-left-controls {max-width: var(--ygl-theater-player-max-width);}ytd-watch-flexy[theater] #movie_player .ytp-right-controls {float: var(--ygl-theater-player-float);}html[youtube-genius-lyrics-container="found"] ytd-watch-flexy[theater] #movie_player video[src],html[youtube-genius-lyrics-container="loading"] ytd-watch-flexy[theater] #movie_player video[src] {left: 0 !important;}html[youtube-genius-lyrics-container] #showlyricsbutton {display: none;}.youtube-genius-lyrics-search-input {-webkit-appearance: none;appearance: none;-webkit-font-smoothing: antialiased;background-color: transparent;border: none;box-shadow: none;color: inherit;font-family: Roboto, Noto, sans-serif;text-align: inherit;border: 1px solid currentColor;box-sizing: border-box;padding: 1px 0;margin: 0;outline-offset: 0px;outline: none;padding: 1px 6px;font-size: 80%;background-color: var(--yt-emoji-picker-search-background-color);border-color: currentColor;margin: 0 6px 0 6px;flex: 1;}[dark] .youtube-genius-lyrics-search-input {border-color: transparent;}.youtube-genius-lyrics-search-input:not(:focus)::placeholder {border-color: var(--yt-emoji-picker-search-placeholder-color);color: var(--yt-emoji-picker-search-placeholder-color);}.youtube-genius-lyrics-search-input.placeholder-lastfetch:not(:focus)::placeholder,.youtube-genius-lyrics-search-input.placeholder-lastfetch:focus::placeholder{color: var(--yt-spec-text-secondary);font-weight: bold;}.youtube-genius-lyrics-search-input:focus,.youtube-genius-lyrics-search-input:active {border-color: var(--paper-checkbox-checked-color);}span.youtube-genius-lyrics-search-search-btn {cursor: pointer;vertical-align: middle;align-self: center;display: inline-flex;margin-left: 0;margin-right: 8px;}.youtube-genius-lyrics-search-search-btn:hover {color: var(--yt-live-chat-toast-action-color);}span.youtube-genius-lyrics-search-search-btn > svg {fill: currentColor;pointer-events: none;touch-action: none;user-select: none;}@keyframes inputNoR###lt {0% {transform: translateX(0px);}25% {transform: translateX(-6px);}50% {transform: translateX(0px);}75% {transform: translateX(6px);}100% {transform: translateX(6px);}}.youtube-genius-lyrics-search-input.lyrics-input-nor###lt {border-color: red;opacity: 0.75;animation: inputNoR###lt 140ms;animation-iteration-count: 3;}.lyrics-searching {/* dont make animation here */filter: blur(0.7px);opacity: 0.85;pointer-events: none;}html {--ygl-lyricsiframe-flex: '--NULL--';--ygl-lyricscontainer-bottom: '--NULL--';--ygl-lyricscontainer-border-radius: '--NULL--';--ygl-lyricscontainer-padding: '--NULL--';--ygl-lyricscontainer-display: '--NULL--';--ygl-lyricscontainer-flex-direction: '--NULL--';--ygl-lyricscontainer-bar-padding: '--NULL--';}.youtube-genius-lyrics-loading-container,.youtube-genius-lyrics-found-container {--ygl-lyricsiframe-flex: 1;--ygl-lyricscontainer-bottom: 4px;/* allow override */--ygl-lyricscontainer-border-radius: 0px 0px 8px 8px;/* allow override */--ygl-lyricscontainer-padding: 0px 8px 8px 8px;/* allow override */--ygl-lyricscontainer-display: flex;--ygl-lyricscontainer-flex-direction: column;--ygl-lyricscontainer-bar-padding: 4px 6px;}.lyricsnavbar {padding: var(--ygl-lyricscontainer-bar-padding);}body #lyricscontainer {display: var(--ygl-lyricscontainer-display);flex-direction: var(--ygl-lyricscontainer-flex-direction);bottom: var(--ygl-lyricscontainer-bottom);border-radius: var(--ygl-lyricscontainer-border-radius);padding: var(--ygl-lyricscontainer-padding);}#lyricsiframe {flex: var(--ygl-lyricsiframe-flex);}#lyricscontainer.youtube-genius-lyrics-loading-container #lyricsiframe {position: absolute;width: 80%;height: 80%;transform: translate(10%, 10%);}#lyricscontainer.youtube-genius-lyrics-loading-container .loadingspinnerholder {position: relative;width: auto !important;top: auto !important;display: flex;flex-direction: column;align-content: center;align-items: center;justify-content: center;overflow: hidden;contain: content;}.youtube-genius-lyrics-search-container {--ygl-lyricscontainer-display: flex;--ygl-lyricscontainer-flex-direction: row;align-items: center;}.youtube-genius-lyrics-r###lts-container {--ygl-lyricscontainer-padding: 2px 6px 6px 6px;}.youtube-genius-lyrics-search-hide-btn,.youtube-genius-lyrics-r###lts-back-btn,.youtube-genius-lyrics-r###lts-hide-btn,.youtube-genius-lyrics-r###lts-config-btn,.youtube-genius-lyrics-search-config-btn {border: 1px solid var(--ytd-searchbox-background);margin: 3px;font-size: 0.83em;color: var(--yt-live-chat-primary-text-color);text-decoration: none;transition: color 400ms;cursor: pointer;}.youtube-genius-lyrics-search-hide-btn:hover,.youtube-genius-lyrics-r###lts-back-btn:hover,.youtube-genius-lyrics-r###lts-hide-btn:hover,.youtube-genius-lyrics-r###lts-config-btn:hover,.youtube-genius-lyrics-search-config-btn:hover {color: var(--yt-live-chat-toast-action-color);text-decoration: none;}.youtube-genius-lyrics-r###lts-line-separator {font-size: 0.83em;color: var(--yt-live-chat-primary-text-color);}.youtube-genius-lyrics-search-label {font-family: cursive;padding: 0px 6px;color: var(--yt-live-chat-primary-text-color);font-size: 80%;}[dark] .youtube-genius-lyrics-search-label {color: var(--yt-live-chat-author-chip-owner-background-color);}.youtube-genius-lyrics-tracklist-info-container{display: flex;column-gap: 6px;flex-direction: row;flex-wrap: nowrap;}.youtube-genius-lyrics-tracklist-info-primary{flex:1;}.youtube-genius-lyrics-tracklist-info-secondary{text-transform: uppercase;font-style: italic;}svg.svg-genius-pageviews {fill: currentColor;width: 10px;margin-right: 2px;}`document.head.appendChild(style)}}const { requestAnimationFrame, setTimeout, setInterval, clearTimeout, clearInterval } = (() => { // eslint-disable-line no-unused-varslet win = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window // eslint-disable-line no-undeflet removeIframeFn = nulltry {const frameId = 'vanillajs-iframe-v1'let frame = document.getElementById(frameId)if (!frame) {frame = document.createElement('iframe')frame.id = frameIdconst blobURL = typeof webkitCancelAnimationFrame === 'function' ? (frame.src = URL.createObjectURL(new Blob([], { type: 'text/html' }))) : null // avoid Brave Crashframe.sandbox = 'allow-same-origin' // script cannot be run inside iframe but API can be obtained from iframelet n = document.createElement('noscript') // wrap into NOSCRPIT to avoid reflow (layouting)n.appendChild(frame)const root = document.documentElementif (root) {root.appendChild(n)if (blobURL) Promise.resolve().then(() => URL.revokeObjectURL(blobURL))removeIframeFn = (setTimeout) => {const removeIframeOnDocumentReady = (e) => {e && win.removeEventListener('DOMContentLoaded', removeIframeOnDocumentReady, false)e = nn = win = removeIframeFn = 0setTimeout ? setTimeout(() => e.remove(), 200) : e.remove()}if (!setTimeout || document.readyState !== 'loading') {removeIframeOnDocumentReady()} else {win.addEventListener('DOMContentLoaded', removeIframeOnDocumentReady, false)}}}}const fc = frame ? frame.contentWindow : nullif (fc) {try {const { requestAnimationFrame, setTimeout, setInterval, clearTimeout, clearInterval } = fcconst res = { requestAnimationFrame, setTimeout, setInterval, clearTimeout, clearInterval }for (const k in res) res[k] = res[k].bind(win) // necessaryif (removeIframeFn) Promise.resolve(res.setTimeout).then(removeIframeFn)return res} catch (e) {if (removeIframeFn) removeIframeFn()}}} catch (e) {console.warn(e)}// since the mechanism does not utilize async/await, use the old method as fallback.function isFakeWindow () {// window is not window in Spotify Web Appreturn (window instanceof window.constructor) === false}/*** getTrueWindow* @returns {Window}*/function getTrueWindow () {// this can bypass Spotify's window Proxy Object and obtain the original window objecttry {return new Function('return window')() // eslint-disable-line no-new-func} catch (e) {console.warn('the actual window object cannot be obtained.', e) // e.g. YouTube Musicreturn window // fallback}}let trueWindow = isFakeWindow() ? getTrueWindow() : winconst { requestAnimationFrame, setTimeout, setInterval, clearTimeout, clearInterval } = trueWindowconst res = { requestAnimationFrame, setTimeout, setInterval, clearTimeout, clearInterval }for (const k in res) res[k] = res[k].bind(trueWindow) // necessarytrueWindow = nullif (removeIframeFn) Promise.resolve(res.setTimeout).then(removeIframeFn)return res})()function appendElements (target, elements) {if (typeof target.append === 'function') {target.append(...elements)} else {for (const element of elements) {target.appendChild(element)}}}const removeElements = (typeof window.DocumentFragment.prototype.append === 'function')? function (elements) {document.createDocumentFragment().append(...elements)}: function (elements) {for (const element of elements) {element.remove()}}function getMastheadHeight () {// can be replaced by youtube css custom propertiesconst masthead = document.querySelector('ytd-masthead#masthead')return masthead.getBoundingClientRect().height}function calcContainerWidthTop () {let wconst upnext = document.querySelector('#secondary #secondary-inner') || document.getElementById('upnext')const playlist = document.querySelector('ytd-playlist-panel-renderer#playlist')const video = document.querySelector('ytd-watch-flexy div#primary video')if (upnext && upnext.getBoundingClientRect().left > 0) {w = window.innerWidth - upnext.getBoundingClientRect().left - 5} else if (playlist && playlist.getBoundingClientRect().left > 0) {w = window.innerWidth - playlist.getBoundingClientRect().left - 5} else if (video) {w = window.innerWidth - 1.02 * video.getBoundingClientRect().right} else {w = window.innerWidth * 0.45}w = Math.min(window.innerWidth * 0.75, w)const top = getMastheadHeight()const isTheatherView = !!document.querySelector('ytd-watch-flexy[theater]')if (isTheatherView) {return {top,width: parseInt(0.3 * document.querySelector('#columns').clientWidth),isTheatherView}} else {return {top,width: w,isTheatherView}}}function setFrameDimensions (container, iframe, bar) { // eslint-disable-line no-unused-vars// if (!container || !iframe || !bar) {//   console.warn('elements not found in setFrameDimensions()')//   return// }// const bar = container.querySelector('.lyricsnavbar')// const width = iframe.style.width = container.clientWidth - 1 + 'px'// const height = iframe.style.height = window.innerHeight - bar.clientHeight - getMastheadHeight() + 'px'/*iframe.style.width = container.clientWidth - 1 + 'px'iframe.style.height = window.innerHeight - bar.clientHeight - getMastheadHeight() + 'px'if (genius.option.themeKey === 'spotify') {iframe.style.backgroundColor = '#181818'bar.style.backgroundColor = '#181818'} else {iframe.style.backgroundColor = ''}*/// return [width, height]}let lastResizeDT = 0function onResize () {const tdt = Date.now()lastResizeDT = tdtsetTimeout(function () {if (tdt === lastResizeDT) {resize()}}, 600)}function resize () {const container = document.getElementById('lyricscontainer')if (!container) {return}const { top, width } = calcContainerWidthTop()// container.style.top = `${top}px`container.style.setProperty('--ygl-container-top', `${top}px`)container.style.top = 'var(--ytd-toolbar-height, var(--ytd-masthead-height, var(--ytd-watch-flexy-masthead-height, var(--ygl-container-top))))' // compatible with YouTube Live Borderlesscontainer.style.setProperty('--ygl-container-width', `${width}px`)// const iframe = document.getElementById('lyricsiframe')// if (iframe) {//   const bar = container.querySelector('.lyricsnavbar')//   if (bar) {//     setFrameDimensions(container, iframe, bar)//   }// }}function getCleanLyricsContainer () {let container = nullconst { top, width } = calcContainerWidthTop()if (!document.getElementById('lyricscontainer')) {container = document.createElement('div')container.id = 'lyricscontainer'document.body.appendChild(container)} else {container = document.getElementById('lyricscontainer')container.textContent = ''container.className = ''container.style = ''}// container.style.top = `${top}px`container.style.setProperty('--ygl-container-top', `${top}px`)container.style.top = 'var(--ytd-toolbar-height, var(--ytd-masthead-height, var(--ytd-watch-flexy-masthead-height, var(--ygl-container-top))))' // compatible with YouTube Live Borderlesscontainer.style.setProperty('--ygl-container-width', `${width}px`)document.body.appendChild(container)const r###lt = document.getElementById('lyricscontainer')if (r###lt !== container) {console.warn(SCRIPT_NAME + ' getCleanLyricsContainer() Could not insert the element correctly')}return r###lt}function hideLyrics () {if (document.querySelector('.loadingspinnerholder') !== null) {genius.f.cancelLoading()}const elementsToBeRemoved = [...document.querySelectorAll('.loadingspinnerholder')]const lyricscontainer = document.getElementById('lyricscontainer')if (lyricscontainer) {elementsToBeRemoved.push(lyricscontainer)}document.documentElement.removeAttribute('youtube-genius-lyrics-container')const isHiding = elementsToBeRemoved.length > 0removeElements(elementsToBeRemoved)addLyricsButton()return isHiding}async function showLyricsButtonClicked () {removeLyricsButton()genius.option.autoShow = true // Temporarily enable showing lyrics automatically on song changeaddLyrics(true)}function addLyricsButton () {if (disableShowLyricsButton === true) returnif (document.getElementById('showlyricsbutton')) {return}// const top = getMastheadHeight()const showlyricsbutton = document.createElement('div')showlyricsbutton.id = 'showlyricsbutton'if (isYouTubeLive) showlyricsbutton.classList.add('hide-during-ytlive')showlyricsbutton.setAttribute('title', 'Load lyrics from genius.com')showlyricsbutton.addEventListener('click', showLyricsButtonClicked, false)document.body.appendChild(showlyricsbutton)}function removeLyricsButton () {let showlyricsbutton = document.getElementById('showlyricsbutton')if (showlyricsbutton !== null) {try {showlyricsbutton.remove()} catch (e) {// do nothing}}showlyricsbutton = null}let lastVideoId = nulllet lastForceVideoId = nulllet hitMaps = nullfunction obtainDataCarouselLockups (ep) {if (!ep) {return null}let m = nulltry {m = ep.engagementPanelSectionListRenderer.content.structuredDescriptionContentRenderer.items[2].videoDescriptionMusicSectionRenderer.carouselLockups} catch (e) {m = null}return m}function getSimpleText (defaultMetadata) {if (!defaultMetadata) {return null}if (typeof defaultMetadata.simpleText === 'string') {return defaultMetadata.simpleText}if (defaultMetadata.runs) {const texts = defaultMetadata.runs.map(entry => entry.text)if (texts.length === 1 && typeof texts[0] === 'string') {return texts[0]}}return null}function simpleTextFixup (text) {return text.replace(/だ/g, 'だ')}function titleFix (text) {return text.replace(/\(([A-Za-z][a-z]+) ([Vv]ersion|[Vv]er\.?)\)/g, '($1)') // Genius Lyrics title use Ver. instead of Version; e.g. English Version}function removeEmojis (str) {return str.replace(/[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F700}-\u{1F77F}\u{1F780}-\u{1F7FF}\u{1F800}-\u{1F8FF}\u{1F900}-\u{1F9FF}\u{1FA00}-\u{1FA6F}\u{1FA70}-\u{1FAFF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}\u{2300}-\u{23FF}\u{2B50}\u{1F004}\u{1F0CF}\u{E0020}-\u{E007F}\u{FE0F}]/gu, '') // eslint-disable-line no-misleading-character-class}function getMusicTitleAndAuthor (pData) {const response = pData.responseconst engagementPanels = response.engagementPanelslet carouselLockups = nullfor (const ep of engagementPanels) {const m = obtainDataCarouselLockups(ep)if (m !== null) {carouselLockups = mbreak}}if (carouselLockups && carouselLockups.length === 1) {let a1 = nulllet a2 = nulltry {a1 = carouselLockups[0].carouselLockupRenderer.infoRows[0].infoRowRenderer.defaultMetadataa2 = carouselLockups[0].carouselLockupRenderer.infoRows[1].infoRowRenderer.defaultMetadata} catch (e) { }a1 = getSimpleText(a1)a2 = getSimpleText(a2)if (a1 && a2 && typeof a1 === 'string' && typeof a2 === 'string') {let title = nulltry {title = pData.playerResponse.videoDetails.title} catch (e) { }if (title && typeof title === 'string') {a1 = removeEmojis(a1).trim().replace(/\s+/g, ' ')a1 = titleFix(simpleTextFixup(a1))a2 = simpleTextFixup(a2)title = removeEmojis(title).trim().replace(/\s+/g, ' ')title = simpleTextFixup(title)const newValue = `${a2} ${a1}`return {title,singer: a2,song: a1,text: newValue}}}} else if (carouselLockups && carouselLockups.length > 1) {setDisableShowLyricsButton(true) // one video with multiple musics}return null}function getYoutubeMainVideo () {let video = document.querySelector('#ytd-player #movie_player video[src]')if (video !== null) {return video}video = document.querySelector('video[src]')if (video !== null) {return video}return null}function isYoutubeVideoPlaying () {const videoPlayerContainer = document.querySelector('#ytd-player #movie_player')let video = nullif (videoPlayerContainer) {const isPaused = videoPlayerContainer.classList.contains('paused-mode')if (isPaused) {return false}const isPlaying = videoPlayerContainer.classList.contains('playing-mode')if (isPlaying) {return true}video = window.HTMLElement.prototype.querySelector.call(videoPlayerContainer, 'video[src]')if (video !== null) {const paused = video.pausedif (paused === true) {return false}if (paused === false) {return true}}}if (video === null) {video = document.querySelector('video[src]')}if (video !== null) {const paused = video.pausedif (paused === true) {return false}if (paused === false) {return true}}}function keywordProcess (title, keywords, kHash) {const upperTitle = title.toUpperCase()return keywords.filter(keywordObj => {if (typeof keywordObj === 'object') {const keyword = keywordObj.keywordreturn title.includes(keyword)}const keyword = keywordObjconst upperKeyword = keyword.toUpperCase()if (!upperTitle.includes(upperKeyword)) return falselet type = kHash.get(keyword)if (!type) {type = 1if (/^[a-zA-Z]+$/.test(keyword)) {type = 3let ignoreCase = falseif (keyword.length > 1) {if ((keyword === keyword.toLowerCase() || keyword === keyword.toUpperCase())) ignoreCase = trueelse if ((keyword === keyword.charAt(0).toUpperCase() + keyword.substring(1).toLowerCase())) ignoreCase = true}if (ignoreCase) type = 7}kHash.set(keyword, type)}if (type === 1) {return title.includes(keyword)} else if (type === 3) {return (new RegExp(`\\b${keyword}\\b`)).test(title)} else if (type === 7) {return (new RegExp(`\\b${keyword}\\b`, 'i')).test(title)}return false})}function makeKeyWords (keywords, songTitle) {keywords = [...keywords]songTitle.replace(/\[([^[\]]+)\]/g, (m, a) => {if (a !== a.trim()) returnkeywords.push(a)})keywords.sort((a, b) => a.length - b.length)let skipNexts = new Set()let newKeyWords = []const upperSongTitle = songTitle.toUpperCase()for (let keyword of keywords) {if (skipNexts.has(keyword)) continuekeyword = keyword.replace(/[\t\r\n]+/g, ' ')const upperKeyWord = keyword.toUpperCase()const j = upperSongTitle.indexOf(upperKeyWord)if (j < 0) continueif (upperKeyWord === 'MV' || upperKeyWord === 'PV' || upperKeyWord === 'SONG' || upperKeyWord === 'MUSIC') continuekeyword = songTitle.substring(j, j + keyword.length)const r = { keyword, upperKeyWord, splitLen: songTitle.split(keyword).length, isBracketed: false, foundAt: j }if (j >= 1) {r.isBracketed = songTitle.charAt(j - 1) === '[' && songTitle.charAt(j + keyword.length) === ']'}skipNexts.add(keyword)newKeyWords.push(r)}skipNexts.clear()skipNexts = nulllet isSkipped = falsefor (const keyword1 of newKeyWords) {for (const keyword2 of newKeyWords) {if (keyword1.keyword.length > keyword2.keyword.length) {if (keyword1.splitLen !== keyword2.splitLen) continueif (keyword1.upperKeyWord.includes(keyword2.upperKeyWord)) {isSkipped = truekeyword2.skip = true}}}}if (isSkipped) newKeyWords = newKeyWords.filter(entry => !entry.skip)newKeyWords.sort((a, b) => a.foundAt - b.foundAt)return newKeyWords}function repeatReplace (text, searchValue, substring, count) {while (count > 0) {const text2 = text.replace(searchValue, substring)if (text2 === text) return texttext = text2count--}return text}async function traditionalYtdDescriptionInfo (videoTitle, videoDetails) {let songArtistslet songTitle = videoTitle// song title text processingsongTitle = removeEmojis(songTitle)songTitle = songTitle.replace(/[\u180E\u200B-\u200D\u2060\uFEFF]+/g, '') // zero-spacing.replace(/[\s\u0009-\u000D\u0020\u0085\u00A0\u1680\u2000-\u200A\u2028-\u2029\u202F\u205F\u3000\u00B7\u237D\u2420\u2422\u2423]+/g, ' ') /* spacing */ // eslint-disable-line no-control-regex// .replace(/[\uFF01-\uFF0F\u0021-\u002F\u003A-\u0040\u005B-\u0060\u007B-\u007E\u3000\u3001-\u303F\u2000-\u206F]+/g, ' ') // Symbols and Punctuation.replace(/│/g, '|').replace(/【([^【】]+)】/g, '[$1]').replace(/\(([^()]+)\)/g, '[$1]').replace(/『([^『』]+)』/g, '[$1]').replace(/「([^「」]+)」/g, '[$1]').replace(/\[(MV|PV)\]/g, '')const keywords = (videoDetails || 0).keywordslet wSongTitle = nullif (videoDetails && keywords && keywords.length > 2) {let variants = new Set()variants.add(songTitle.trim())for (const s of songTitle.split('/')) {variants.add(s.trim())}for (const s of songTitle.split('|')) {// 『チェンソーマン』第9話ノンクレジットエンディング / CHAINSAW MAN #9 Ending│Aimer「Deep down」variants.add(s.trim())}variants = [...variants.keys()]let variantsX = new Map()let kHash = new Map()const mainwords = keywordProcess(songTitle, keywords, kHash)kHash.clear()kHash = nullif (mainwords.length > 2) {const vKeywords = makeKeyWords(mainwords, songTitle)console.log(vKeywords)await Promise.all(variants.map(variant => {return new Promise(resolve => {const mainwords = keywordProcess(variant, vKeywords, null)let kMatch = 0let kBracket = 0for (const keywordObj of mainwords) {kMatch++if (keywordObj.isBracketed) kBracket++}// console.log(22,variant,kMatch, kBracket, mainwords)// 【MV】迷星叫 / MyGO!!!!!【オリジナル楽曲】// 【歌ってみた】大脳的なランデブー / Covered by 花鋏キョウ【Kanaria】if (kMatch === 2 || kMatch - kBracket === 2) {const m = kMatch === 2 ? mainwords : mainwords.filter(entry => !entry.isBracketed)const p = `${m[0].keyword}\t${m[1].keyword}`const lastAdded = variantsX.get(p)if (!lastAdded || (lastAdded && lastAdded.variant.length > variant.length)) variantsX.set(p, { variant, kMatch, kBracket }) // store the shortest match}resolve(0)})}))}if (variantsX.size === 1) {const values = [...variantsX.keys()]wSongTitle = values[0]} else if (variantsX.size > 1) {const entries = [...variantsX.entries()]entries.sort((a, b) => (b[1].kMatch * 100 + b[1].kBracket) - (a[1].kMatch * 100 + a[1].kBracket))if (entries[0][1].kMatch > entries[1][1].kMatch) {wSongTitle = entries[0][0]}}variantsX.clear()variantsX = nullvariants.length = 0variants = null}if (wSongTitle !== null) {const m = wSongTitle.split('\t')window.defaultSongTitle = `${m[0]} ${m[1]}`return { songTitle: wSongTitle, songArtistsArr: null }}// Symbols and Punctuation can be part of the artist name (e.g. &TEAM, milli-billi)songTitle = simpleTextFixup(songTitle)songTitle = songTitle.replace(/(Official )?(Lyrics Video|Lyric Video|Music Video|Promotion Video|Video)/gi, '')songTitle = songTitle.replace(/\b(PERFORMANCE VIDEO|official mv|karaoke mv|official|music mv|audio|music|video|karaoke)\b/gi, '')songTitle = songTitle.replace(/\(\s*\)|\[\s*\]/g, '')songTitle = songTitle.replace(/exclusive\s*-?/gi, '')songTitle = songTitle.replace(/-\s*-/gi, ' - ')songTitle = songTitle.replace(/([\uFF01-\uFF0F\u0021-\u002F\u003A-\u0040\u005B-\u0060\u007B-\u007E\u3000\u3001-\u303F\u2000-\u206F\s])(MV|PV)\s*$/g, '$1')songTitle = songTitle.replace(/^\s*(MV|PV)([\uFF01-\uFF0F\u0021-\u002F\u003A-\u0040\u005B-\u0060\u007B-\u007E\u3000\u3001-\u303F\u2000-\u206F\s])/g, '$2')songTitle = repeatReplace(songTitle, /\[\]/g, '', 8)songTitle = songTitle.trim()// YOASOBI[祝福] [[機動戦士ガンダム 水星の魔女]オープニングテーマ]// 結束バンド[星座になれたら]/ TVアニメ[ぼっち・ざ・ろっく!]第12話劇中曲const exec1 = /^[^[\](){}]+\[[^[\](){}]+\]/.exec(songTitle)if (exec1) {let tmpSongTitle = songTitletmpSongTitle = tmpSongTitle.replace(/\[\[[^[\]{}()]+\](オープニングテーマ|オープニング|エンディングテーマ|エンディング|Opening\s?Song|Ending\s?Song|Opening|Ending)\]$/, ' ').trim()tmpSongTitle = tmpSongTitle.replace(/\[[^[\]{}()]*\s*(オープニングテーマ|オープニング|エンディングテーマ|エンディング|Opening\s?Song|Ending\s?Song|Opening|Ending)\]$/, ' ').trim()tmpSongTitle = tmpSongTitle.replace(/(TV)?\s*(アニメ|Anime)\[[^[\]{}()]+\][^[\]{}()]*$/, ' ').trim()if (tmpSongTitle.includes(exec1[0])) {songTitle = tmpSongTitle}}songTitle = repeatReplace(songTitle, /[//]\s*$/g, '', 8)// Pattern: Artist  - Song titlesongTitle = songTitle.split(/\s+[-–]\s+/)if (songTitle.length === 1) {// Pattern: Artist[Song title]const m = songTitle[0].match(/^([^[\]{}()]+)\[([^[\]{}()]+)\]$/)if (m) {songTitle = [m[1], m[2]]}}if (songTitle.length === 1) {// Pattern: Artist | Song titleconst m = songTitle[0].match(/(.+?)\s*\|\s*(.+)/)if (m) {songTitle = [m[1], m[2]]}}if (songTitle.length === 1) {// Pattern: Artist "Song title"const m = songTitle[0].match(/(.+?)\s*["“”'`´*]+(.+)["“”'`´*]+/)if (m) {songTitle = [m[1], m[2]]}}if (songTitle.length === 1) {// Pattern: Songtitle by Artistconst m = songTitle[0].match(/(.+?)\s+by\s+(.+)/)if (m) {songTitle = [m[2], m[1]]}}if (songTitle.length === 1 && 'author' in videoDetails) {// Fallback to video author namesongArtists = videoDetails.authorsongArtists = songArtists.replace(/\b(vevo|official|music|channel)\b/gi, '')songArtists = songArtists.replace(/-\s*topic/gi, '')} else {songArtists = songTitle.shift().trim()}const songArtistsArr = songArtists.split(',').map(s => s.trim())songTitle = songTitle.join(' - ').trim()songTitle = genius.f.cleanUpSongTitle(songTitle)// return object r###ltreturn { songTitle, songArtistsArr }}function newYtdDescriptionInfo (ytdDescriptionInfo) {let song = ytdDescriptionInfo.songconst singer = ytdDescriptionInfo.singersong = song.replace('(Karaoke)', '')// return object r###ltreturn { songTitle: `${singer}\t${song}`, songArtistsArr: null }}function getYtdAppData () {const ytdApp = document.querySelector('ytd-app')return (ytdApp.__data || 0).data || ytdApp.data || null}function isYtdAppReady () {let ytdAppData = nulllet videoDetails = nulltry {ytdAppData = getYtdAppData()if ('player' in ytdAppData && 'args' in ytdAppData.player && 'raw_player_response' in ytdAppData.player.args && 'videoDetails' in ytdAppData.player.args.raw_player_response) {videoDetails = ytdAppData.player.args.raw_player_response.videoDetails} else {videoDetails = ytdAppData.playerResponse.videoDetails}} catch (e) {}return !!videoDetails}function getVideoInfo (ytdAppData) {let videoDetails = nulltry {if ('player' in ytdAppData && 'args' in ytdAppData.player && 'raw_player_response' in ytdAppData.player.args && 'videoDetails' in ytdAppData.player.args.raw_player_response) {videoDetails = ytdAppData.player.args.raw_player_response.videoDetails || null} else {videoDetails = ytdAppData.playerResponse.videoDetails || null}} catch (e) {console.warn(SCRIPT_NAME + ' addLyrics() Could not find videoDetails')console.log(e)const m = document.location.href.match(/v=(\w+)&?/)videoDetails = {videoId: (m && m[1] ? m[1] : ''),keywords: [],shortDescription: ''}}return videoDetails // could be null}async function getPageSongInfo (ytdAppData, videoDetails) {let isMusic = falselet ytdDescriptionInfo = nulllet videoTitle = nulllet genre = nulllet isFamilySafe = null// obtain the music info from modern meta panelytdDescriptionInfo = getMusicTitleAndAuthor(ytdAppData)if (ytdDescriptionInfo !== null) {isMusic = true}// videoTitletry {videoTitle = getSimpleText(ytdAppData.playerResponse.microformat.playerMicroformatRenderer.title)} catch (e) { }// genretry {genre = ytdAppData.playerResponse.microformat.playerMicroformatRenderer.category} catch (e) { }if (typeof videoTitle !== 'string') {return null}console.log(`Youtube Genius Lyrics - Genre "${genre}" is found`)if (ytdDescriptionInfo === null) {if (genre === 'Music') {isMusic = true}}// isFamilySafetry {isFamilySafe = ytdAppData.playerResponse.microformat.playerMicroformatRenderer.isFamilySafe} catch (e) { }if (isFamilySafe === false) return null // not suitable to load lyrics; isFamilySafe shall not be false for musicwindow.defaultSongTitle = null// traditionalYtdDescriptionInfo if ytdDescriptionInfo is not availableconst { songTitle, songArtistsArr } = (ytdDescriptionInfo === null)? await traditionalYtdDescriptionInfo(videoTitle, videoDetails): newYtdDescriptionInfo(ytdDescriptionInfo)// console.log(window.defaultSongTitle)// if (!isMusic && window.defaultSongTitle) isMusic = truereturn { songTitle, songArtistsArr, isMusic }}function setDisableShowLyricsButton (newValue) {if (newValue === disableShowLyricsButton) returndisableShowLyricsButton = newValueif (disableShowLyricsButton === true) {removeLyricsButton()}// note: setting to false would not immediately generate the button}let mPageSongInfoPromise = nullasync function addLyrics (force, beLessSpecific) {const isMainCall = arguments.length === 0if (isMainCall) setDisableShowLyricsButton(false) // reset to false for every addLyrics()try {const pRes = await Promise.race([mPageSongInfoPromise])if (!pRes) return // unknown errorswitch (pRes.status) {case -1: // rarely happen; only if addLyrics triggered when the page is still loadingreturncase -2: // Not a video page, video page not visible, or any case that should disable the featurelastVideoId = nullif (isMainCall) setDisableShowLyricsButton(true) // no button if the page is confirmed as non-video page like 'browse', 'search'genius.f.hideLyricsWithMessage()returncase -3: // no video details or videoIdlastVideoId = nullif (isMainCall) setDisableShowLyricsButton(false) // try to get page info again when user clicks the buttonelse if (force === true) setDisableShowLyricsButton(true) // no video details when the button is clickedgenius.f.hideLyricsWithMessage()returndefault:// do nothing}const tmpVideoId = `${pRes.videoId}${genius.option.themeKey}${genius.option.fontSize}`if (lastVideoId === tmpVideoId && document.getElementById('lyricscontainer')) {// Same video id and same theme settings and lyrics are showing -> stop herereturn}lastVideoId = tmpVideoIdconst pageSongInfoRes = pRes.pageSongInfoRes // can be null// setDisableShowLyricsButton(true) might have called in getPageSongInfoif (!pageSongInfoRes) {// video id is known but the song info is unknown (or not suitable to show lyrics)// do not load lyrics even if force = truesetDisableShowLyricsButton(true) // the page is loaded but no song info; disable the buttongenius.f.hideLyricsWithMessage()return}let { songTitle, songArtistsArr, isMusic } = pageSongInfoResif (force) {isMusic = truelastForceVideoId = lastVideoId} else if (isMusic === false && (lastForceVideoId === null || lastForceVideoId !== lastVideoId)) {// show button if not disabledgenius.f.hideLyricsWithMessage()return}const musicIsPlaying = isYoutubeVideoPlaying()genius.f.loadLyrics(force, beLessSpecific, songTitle, songArtistsArr, musicIsPlaying)} catch (e) {console.warn(e)}}let lastPos = nullfunction cubicBezier (p1x, p1y, p2x, p2y) {const p1 = {x: p1x,y: p1y}const p2 = {x: p2x,y: p2y}function w (t, u0, u1) {const f = 1 - treturn 3 * f * f * t * u0 + 3 * f * t * t * u1 + t * t * t}function v (t, A, B, C) {return A * t * t * t + B * t * t + C * t}function vp (t, A, B, C) {return 3 * A * t * t + 2 * B * t + C}/* eslint-disable camelcase */const w_n1 = w(-1, p1.x, p2.x)const w_p1 = w(1, p1.x, p2.x)const w_p2 = w(2, p1.x, p2.x)const w_B = (w_n1 + w_p1) / 2const w_6A2B = w_p2 - 2 * w_p1 // 8-2; 4-2; 2-2const w_A = (w_6A2B - 2 * w_B) / 6const w_C = w_p1 - w_A - w_B/* eslint-enable camelcase */// Ax^3. Bx^2. Cx + 0// -1: -A + B - C// 1: A + B + C// 2: 8A + 4B + 2C// Ax^3 + Bx^2 + Cx + 0 = s// Ax^3 + Bx^2 + Cx - s = 0/* eslint-disable camelcase */let last_s = nulllet last_t = nulllet last_kvp = nullreturn function cbpt (s) {if (s > 0 && s < 1) {let t = s // guess t0=s instead of t0=0.5if (last_s !== null) {// t_n = t_n-1 - (kv - s) / kvp = t_n-1 - kv / kvp + s / kvp// t'_0 = t_n-1 - (kv - s') / kvp = t_n-1 - kv / kvp + s' / kvp// t'_0 = t_n - s / kvp + s' / kvp = t_n + (s' - s) / kvpt = last_t + (s - last_s) / last_kvp}let ulet i = 0let kvp = 0.0while (i < 2) {const kv = v(t, w_A, w_B, w_C)kvp = vp(t, w_A, w_B, w_C)const dt = (kv - s) / kvpt -= dtif (i > 0 && u < 0 && t < 0) {// do nothing} else if (i > 0 && u > 1 && t > 1) {// do nothing} else if (dt * dt < 0.00001) {i++}u = t}last_t = tlast_s = slast_kvp = kvpreturn w(t, p1.y, p2.y)} else if (s >= 0 && s <= 1) { // avoid equal comparision for floating valuesreturn s} else {return null}}/* eslint-enable camelcase */}// https://cubic-bezier.com/#const cbLyricsTime = cubicBezier(.21, .08, .42, .66) /* eslint-disable-line no-floating-decimal */// the adjustment is based on typical JPOP songs:// 美波「カワキヲアメク」MV https://www.youtube.com/watch?v=0YF8vecQWYs// GHOST / 星街すいせい(official) https://www.youtube.com/watch?v=IKKar5SS29E// at the begining, slower to suit either with or without verse.// for verse at the beginning, the lyrics will be upshifted for the first sentence// for without verse at the beginning, the singer would not sing in a very fast pace, so it can be slowed down// it usually singing ends before the media ends. it might be pure music to make listeners relax the emotion.// the lyrics shall be ahead the timeline a bit// verse at the middle would not affect much. It will be equvalent to ~3 lines scrolling.// Usually there is blank line and new line with the word "[verse]" such that the scrolling will still match the lyricslet isUpdateAutoScrollBusy = falseasync function updateAutoScroll (video, force) {if (isUpdateAutoScrollBusy) returnif (isTriggered !== true) return // not readyif (!genius.current.compoundTitle) return // not readyif (!video) {video = getYoutubeMainVideo()if (!video) return}isUpdateAutoScrollBusy = trueconst { currentTime, duration } = videolet pos = currentTime / durationif (pos >= 0) {// do nothing} else {isUpdateAutoScrollBusy = falsereturn // invalid currentTime or duration}if (`${lastPos}` !== `${pos}`) {lastPos = poslet ct = currentTimeif (force !== true) {await new Promise(resolve => setTimeout(resolve, 30))const ct1 = video.currentTimeif (`${video.duration}` === `${duration}` && ((ct1 - ct < 50 / 1000 && ct1 > ct) || `${ct1}` === `${ct}`)) {// if the video is playing or stopped, without change of mediact = ct1pos = ct / duration} else {isUpdateAutoScrollBusy = falsereturn // invalid timechange}}if (duration > 15) { // skip for music <= 15slet k = 1.95 // the scrollbar will just disappear at the end of musicif (duration > 80) {k = 3.21 // the singer shall stop a bit eariler than the media endsif (duration > 160) {k = 4.82}}// p0 = (d-k)/d// p1 = p0 + (m/d)*p0// p1 = d/d = 1// 1 - (d-k)/d = (m/d) * p0// k/d = m*p0/d// m = k/p0 = kd/(d-k)const m = k * duration / (duration - k) // offset at pos = 1.0const timelineOffset = m * pos // end scrolling earlier than video end by ${k}s; the scrollbar will disappear at the end of musicct += timelineOffsetif (ct < 0) ct = 0if (ct > duration) ct = duration// ct=0; ct'=0// ct=y; ct'=ylet cbFactor = 1.0if (ct > 0 && ct < duration) {const s = ct / duration // (0,1)const s1 = await cbLyricsTime(s) // (0,1)cbFactor = s1 / s}pos = ct / duration * cbFactor}genius.f.scrollLyrics(pos)}await new Promise(requestAnimationFrame) /* eslint-disable-line no-new */isUpdateAutoScrollBusy = false}function safeString (s) {return (s || 0).length > 0 ? `${s}` : ''}async function performSearch () {try {const container = document.querySelector('#lyricscontainer.youtube-genius-lyrics-search-container')const input = document.querySelector('input.youtube-genius-lyrics-search-input')if (!container || !input) returnlet inputValue = input.valueif (!input.value) {inputValue = placeholderValue()window.lastUserInput = null} else {window.lastUserInput = inputValue}if (inputValue) {if (document.querySelector('.loadingspinnerholder') !== null) {genius.f.cancelLoading()}if (typeof genius.current.compoundTitle === 'string') {genius.f.forgetLyricsSelection(genius.current.compoundTitle, null)}try {input.blur()} catch (e) { }await Promise.resolve(0)container.classList.add('lyrics-searching')genius.f.searchByQuery(inputValue, container, (res) => {let c = document.querySelector('#lyricscontainer')try {if (res && res.status === 200) {const hits = res.hitsif (!hits) returnif (hits.length > 0) {listSongs(hits, container, inputValue)} else {input.classList.add('lyrics-input-nor###lt')window.incorrectInputValue = inputValue}}} catch (e) { }if (c !== null) {c.classList.remove('lyrics-searching')}c = null})} else {input.classList.add('lyrics-input-nor###lt')window.incorrectInputValue = ''}} catch (e) {console.warn(e)}}function onSearchLyricsKeyDown (ev) {const input = thisif (typeof window.incorrectInputValue === 'string' && input.value !== window.incorrectInputValue) {window.incorrectInputValue = nullinput.classList.remove('lyrics-input-nor###lt')}}function onSearchLyricsKeyUp (ev) {const input = thisif (ev.code === 'Escape') {ev.preventDefault()input.value = ''} else if (ev.code === 'Enter' || ev.code === 'NumpadEnter') {ev.preventDefault()performSearch()return}if (typeof window.incorrectInputValue === 'string' && input.value !== window.incorrectInputValue) {window.incorrectInputValue = nullinput.classList.remove('lyrics-input-nor###lt')}}function onSearchLyricsSearchBtnClick (ev) {ev.preventDefault()performSearch()}function hideLyricsWithMessageAndStopAutoShow () {if (genius.option.autoShow === true) {const isLyricsPanelShown = lyricsDisplayState === 'loading' || lyricsDisplayState === 'loaded'if (isLyricsPanelShown) genius.option.autoShow = false // Temporarily disable showing lyrics automatically on song change}genius.f.hideLyricsWithMessage()}function onSearchLyricsHideBtnClick (ev) {hideLyricsWithMessageAndStopAutoShow()}function placeholderValue () {return safeString(window.lastUserInputConfirmed) || safeString(window.lastFetchedQuery) || safeString(window.defaultSongTitle) || ''}function showSearchField (query) {const spanLabel = document.createElement('span')spanLabel.classList.add('youtube-genius-lyrics-search-label')spanLabel.textContent = 'Search genius.com: 'const input = document.createElement('input')input.classList.add('youtube-genius-lyrics-search-input')// input.placeholder = 'Search genius.com...'const searchBtn = document.createElement('span')searchBtn.classList.add('youtube-genius-lyrics-search-search-btn')searchBtn.appendChild(elmBuildNS('svg',{xmlns: 'http://www.w3.org/2000/svg'},{attr: {width: '12',height: '12',preserveAspectRatio: 'xMidYMid meet',viewBox: '0 0 342 342'}}, ['path', {attr: {d: 'M337 317 239 219C259 196 270 166 270 134 270 60 210 0 135 0S0 60 0 134 61 268 135 268C167 268 196 257 219 239L317 337C323 343 331 343 337 337S343 323 337 317ZM29 134C29 76 76 29 135 29S241 76 241 134 194 239 135 239 29 192 29 134Z'}}]))// Hide buttonconst hideButton = document.createElement('span')hideButton.classList.add('youtube-genius-lyrics-search-hide-btn')hideButton.textContent = 'Hide'hideButton.addEventListener('click', onSearchLyricsHideBtnClick, false)const configButton = createConfigBtn('youtube-genius-lyrics-search-config-btn')input.value = safeString(query) || ''if (typeof window.lastUserInput === 'string' && !input.value) {input.value = safeString(window.lastUserInput) || ''}const fetechQuery = placeholderValue()input.placeholder = fetechQuery || `${genius.option.defaultPlaceholder}`input.classList.toggle('placeholder-lastfetch', !!fetechQuery)let b = null // containerwindow.incorrectInputValue = nullinput.addEventListener('keydown', onSearchLyricsKeyDown, false)input.addEventListener('keyup', onSearchLyricsKeyUp, false)searchBtn.addEventListener('click', onSearchLyricsSearchBtnClick, false)// flush DOMb = getCleanLyricsContainer()b.classList.add('youtube-genius-lyrics-search-container')document.documentElement.setAttribute('youtube-genius-lyrics-container', 'search')appendElements(b, [spanLabel,input,searchBtn,configButton,hideButton])document.body.appendChild(b)new Promise(() => { /* eslint-disable-line no-new */input.focus()})}function createConfigBtn (cName) {const configButton = document.createElement('span')configButton.classList.add(cName)configButton.textContent = 'Options'configButton.addEventListener('click', function configButtonClick (ev) {genius.f.config()})return configButton}function onLyricsFoundHideBtnClick (ev) {hideLyricsWithMessageAndStopAutoShow()}function onLyricsFoundBackToSearchClick (ev) {showSearchField()}function setupLyricsDisplayDOM (song, searchr###ltsLengths) { // eslint-disable-line no-unused-vars// getCleanLyricsContainerconst container = getCleanLyricsContainer()container.className = '' // custom.getCleanLyricsContainer might forget to clear the className if the element is reusedcontainer.classList.add('youtube-genius-lyrics-found-container')document.documentElement.setAttribute('youtube-genius-lyrics-container', 'found')if (typeof genius.f.isGreasemonkey === 'function' && genius.f.isGreasemonkey()) {// container.textContent = '';elmBuild(container,['h2','This script only works in ',['a', {target: '_blank',href: 'https://addons.mozilla.org/en-US/firefox/addon/tampermonkey/'}, 'Tampermonkey']],'Greasemonkey is no longer supported because of this ',['a', {target: '_blank',href: 'https://github.com/greasemonkey/greasemonkey/issues/2574'}, 'bug greasemonkey/issues/2574'],' in Greasemonkey.')return}let separator = document.createElement('span')separator.classList.add('second-line-separator')separator.classList.add('youtube-genius-lyrics-found-separator')separator.textContent = '•'const bar = document.createElement('div')bar.classList.add('lyricsnavbar')/*// Resize buttonif ('initResize' in custom) {const resizeButton = document.createElement('span')resizeButton.style.fontSize = '1.8em'resizeButton.style.cursor = 'ew-resize'resizeButton.textContent = '⇹'resizeButton.addEventListener('mousedown', custom.initResize)elementsToBeAppended.push(resizeButton, separator.cloneNode(true))}*/// Hide buttonconst hideButton = document.createElement('span')hideButton.classList.add('youtube-genius-lyrics-found-hide-btn')hideButton.textContent = 'Hide'hideButton.addEventListener('click', onLyricsFoundHideBtnClick, false)// Config buttonconst configButton = createConfigBtn('youtube-genius-lyrics-found-config-btn')let goToSearchBtn = nullif (searchr###ltsLengths === 1) {// Wrong lyrics buttonconst wrongLyricsButton = document.createElement('span')wrongLyricsButton.classList.add('youtube-genius-lyrics-found-wonglyrics-btn')wrongLyricsButton.textContent = 'Search lyrics'wrongLyricsButton.addEventListener('click', onLyricsFoundBackToSearchClick, false)goToSearchBtn = wrongLyricsButton} else if (searchr###ltsLengths > 1) {// Back buttonconst backbutton = document.createElement('span')backbutton.classList.add('youtube-genius-lyrics-found-back-btn')backbutton.textContent = `Back to search (${searchr###ltsLengths - 1} other r###lt${searchr###ltsLengths === 2 ? '' : 's'})`backbutton.addEventListener('click', onLyricsFoundBackToSearchClick, false)goToSearchBtn = backbutton}const iframe = document.createElement('iframe')if (typeof webkitCancelAnimationFrame === 'function') {iframe.src = (iframeBlankURL || (iframeBlankURL = URL.createObjectURL(new Blob([], { type: 'text/html' })))) // Brave}iframe.id = 'lyricsiframe'iframe.style.opacity = 0.1// flush to DOM treeappendElements(bar, [goToSearchBtn,separator.cloneNode(true),configButton,separator.cloneNode(true),hideButton])appendElements(container, [bar, iframe])// clean upseparator = nullreturn {container,bar,iframe}}function getHitOfElement (li) {if (!li || li.nodeType !== 1 || !hitMaps) {return null}return hitMaps.get(li) || null}// function formatPageViews (stats) {//   if (!stats) return null//   return 'pageviews' in stats && typeof stats.pageviews === 'number' ? genius.f.metricPrefix(stats.pageviews, 1) : ' - '// }async function rememberLyricsSelection (title, artists, hit) {// in order to call "genius.f.rememberLyricsSelection(title, artists, jsonHit)", use async call to get jsonHitconst jsonHit = await new Promise(function (resolve) { /* eslint-disable-line no-new */// this is not a complete async function, but it helps not to block the scriptingresolve(JSON.stringify(hit))})genius.f.rememberLyricsSelection(title, artists, jsonHit)}function onLyricsR###ltsBackBtnClick (ev) {showSearchField()}function onLyricsR###ltsHideBtnClick (ev) {hideLyricsWithMessageAndStopAutoShow()}function onLyricsR###ltsTrackListClick (ev) {const tracklist = thisconst element = ev.targetif (element.nodeName === 'LI') {const hit = getHitOfElement(element)if (hit !== null) {if (typeof window.lastUserInput === 'string') {window.lastUserInputConfirmed = window.lastUserInputwindow.lastUserInput = null}const compoundTitle = genius.current.compoundTitleconst searchr###ltsLengths = tracklist.querySelectorAll('li').lengthgenius.f.showLyrics(hit, searchr###ltsLengths)rememberLyricsSelection(compoundTitle, null, hit)}}}function convertPageViewsToText (pageviews) {if (!Number.isFinite(pageviews)) return 'NaN'if (pageviews <= 999) {return pageviews}if (pageviews <= 999949) {return `${+(pageviews / 1000).toFixed(1)}K`}// if(pageviews<=999949999){return `${+(pageviews / 1000000).toFixed(1)}M`// }}function listSongs (hits, container, query) {// Back to search buttonconst backToSearchButton = document.createElement('span')backToSearchButton.classList.add('youtube-genius-lyrics-r###lts-back-btn')backToSearchButton.textContent = 'Back to search'backToSearchButton.addEventListener('click', onLyricsR###ltsBackBtnClick, false)const separator = document.createElement('span')separator.classList.add('second-line-separator')separator.classList.add('youtube-genius-lyrics-r###lts-line-separator')separator.textContent = '•'// Hide buttonconst hideButton = document.createElement('span')hideButton.classList.add('youtube-genius-lyrics-r###lts-hide-btn')hideButton.textContent = 'Hide'hideButton.addEventListener('click', onLyricsR###ltsHideBtnClick, false)// Config buttonconst configButton = createConfigBtn('youtube-genius-lyrics-r###lts-config-btn')// List search r###ltsconst tracklistOL = document.createElement('ol')tracklistOL.classList.add('tracklist')tracklistOL.classList.add('youtube-genius-lyrics-r###lts-tracklist')tracklistOL.addEventListener('click', onLyricsR###ltsTrackListClick, true)let autoHit = autoSelectLyrics(hits) // setup _matchSourceif (autoHit) autoHit = autoHit.hitconst isTopR###ltBeingAutoHit = autoHit === hits[0]// prepare r###ltsconst liArr = hits.map(function hitsMap (hit) {const li = document.createElement('li')li.classList.add('youtube-genius-lyrics-r###lts-li')const r###ltUrl = hit.r###lt.urlconst r###ltPageViews = (hit.r###lt.stats || 0).pageviewsli.setAttribute('data-r###lt-url', r###ltUrl)li.setAttribute('data-r###lt-pageviews', r###ltPageViews)// li.setAttribute('title', `${Number.isFinite(r###ltPageViews) ? convertPageViewsToText(r###ltPageViews) + "\n" : ""}${r###ltUrl}`)if (isTopR###ltBeingAutoHit && hit._order === hits[0]._order && hit._matchScore === hits[0]._matchScore) {li.classList.add('lyrics-major-r###lt')} else {li.classList.add('lyrics-minor-r###lt')}li.setAttribute('title', `${hit.r###lt.title_with_featured}`)const showPageViews = true // no need to show this; pageviews usually NaNconst showLyricsState = true/* eslint-disable operator-linebreak, indent, multiline-ternary */elmBuild(li,['div',['div', { classList: ['onhover'] }, ['span', '🅖']],['div', { classList: ['onout'] }, ['span', '📄']]],['div', { classList: ['youtube-genius-lyrics-tracklist-info-container'] },['p', { classList: ['youtube-genius-lyrics-tracklist-info-primary'] }, `${hit.r###lt.primary_artist.name} • ${hit.r###lt.title}`],showPageViews && showLyricsState && Number.isFinite(r###ltPageViews) ?['p', { classList: ['youtube-genius-lyrics-tracklist-info-secondary'] },['span', { style: { 'font-size': '0.7em' } },elmBuildNS('svg', { classList: ['svg-genius-pageviews'] }, { attr: { viewBox: '-38 -27 76 54' } },['path', { attr: { d: 'M0-25C21-25 35 0 35 0S21 25 0 25-35 0-35 0-21-25 0-25zm0 7C-13-18-23-6-28 0c5 6 15 18 28 18S23 6 28 0C23-6 13-18 0-18M0-7a7 7 90 1 1-7 7 7 7 90 0 1 7-7m0-5A12 12 90 1 0 12 0 12 12 90 0 0 0-12z' } }]),`${convertPageViewsToText(r###ltPageViews) || ''} ${hit.r###lt.lyrics_state}`]] : '',!showPageViews && showLyricsState ? ['p', { classList: ['youtube-genius-lyrics-tracklist-info-secondary'] }, ['span', { style: { 'font-size': '0.7em' } }, `${hit.r###lt.lyrics_state}`]] : ''],['div', { style: { clear: 'left' } }])/* eslint-enable operator-linebreak, indent, multiline-ternary */if (!hitMaps) {hitMaps = new WeakMap()}hitMaps.set(li, hit)return li})appendElements(tracklistOL, liArr)// Flush DOMif (!container) {container = getCleanLyricsContainer()} else {container.className = ''container.textContent = ''}container.classList.add('youtube-genius-lyrics-r###lts-container')document.documentElement.setAttribute('youtube-genius-lyrics-container', 'r###lts')appendElements(container, [backToSearchButton,separator,configButton,separator.cloneNode(true),hideButton,tracklistOL])}function loremIpsum () {const random = (x) => 1 + parseInt(Math.random() * x)// Create a container for the entire contentconst container = document.createElement('div')for (let v = 0; v < Math.max(3, random(5)) + 4; v++) {for (let b = 0; b < random(6); b++) {const lineContainer = document.createElement('span')lineContainer.classList.add('gray')for (let l = 0; l < random(9); l++) {for (let w = 0; w < 1 + random(10); w++) {for (let i = 0; i < 1 + random(7); i++) {// Create and append 'x' text nodeconst xTextNode = document.createTextNode('x')lineContainer.appendChild(xTextNode)}// Add the whitespace spanconst whiteSpaceSpan = document.createElement('span')whiteSpaceSpan.classList.add('white')whiteSpaceSpan.textContent = '\u00A0' // Non-breaking spacelineContainer.appendChild(whiteSpaceSpan)}// Add line break (br) after each setlineContainer.appendChild(document.createElement('br'))}// Append the line container to the main containercontainer.appendChild(lineContainer)// Add a line break after each sectioncontainer.appendChild(document.createElement('br'))}}return container // Return the main container with all generated elements}function createSpinner (spinnerHolder) {lyricsDisplayState = 'loading'const lyricscontainer = document.getElementById('lyricscontainer')lyricscontainer.className = ''lyricscontainer.classList.add('youtube-genius-lyrics-loading-container')document.documentElement.setAttribute('youtube-genius-lyrics-container', 'loading')const spinner = spinnerHolder.appendChild(document.createElement('div'))spinner.classList.add('loadingspinner')const lorem = loremIpsum()lorem.classList.add('lorem', 'lorem-scroll')spinnerHolder.appendChild(lorem)return spinner}function customSpinnerDOM (container, bar, iframe) {const spinnerDOM = {createSpinnerHolder: () => {const spinnerHolder = document.createElement('div')spinnerHolder.classList.add('loadingspinnerholder')spinnerDOM.spinnerHolder = spinnerHolder},createSpinner: () => {let spinner = nullconst spinnerHolder = spinnerDOM.spinnerHolderspinner = createSpinner(spinnerHolder)spinnerDOM.spinner = spinner},displaySpinnerHolder: () => {container.appendChild(spinnerDOM.spinnerHolder)},setStatusTitle: (title) => {const spinnerHolder = spinnerDOM.spinnerHolderspinnerHolder.title = title},setSpinnerNum: (text) => {const spinner = spinnerDOM.spinnerspinner.textContent = text},remove: (text) => {const spinnerHolder = spinnerDOM.spinnerHolderspinnerHolder.remove()}}return spinnerDOM}function iframeLoadedCallback1 (res) {lyricsDisplayState = 'loaded'}function iframeLoadedCallback2 (res) {// nothing}function textSlash (text) {// Create a set object which contains the information of the titletext = text.replace(/\b([a-z0-9A-Z]+)[:~\-+]([a-z0-9A-Z]+)\b/, '$1$3').replace(/[\uFF01-\uFF5E]/g, (m) => {// Halfwidth and Fullwidth Formsreturn String.fromCharCode(m.charCodeAt(0) - 65248)}).replace(/[\u180E\u200B-\u200D\u2060\uFEFF]+/g, '') // zero-spacing.replace(/[\s\u0009-\u000D\u0020\u0085\u00A0\u1680\u2000-\u200A\u2028-\u2029\u202F\u205F\u3000\u00B7\u237D\u2420\u2422\u2423]+/g, '/') /* spacing */ // eslint-disable-line no-control-regex.replace(/[\uFF01-\uFF0F\u0021-\u002F\u003A-\u0040\u005B-\u0060\u007B-\u007E\u3000\u3001-\u303F\u2000-\u206F]+/g, '/') // Symbols and Punctuation.replace(/\/+/g, '/')let s = text.split('/')const r = new Set()for (let t of s) {if (t && t.length > 0) {t = t.toLowerCase()if (!r.has(t)) {r.add(t)}}}s = nullreturn r}function autoSelectLyrics (hits) {// only do title matching (+channel name)// to search the lyrics, use the short title// to figure out which one is the most correct one, use full / featured titleconst ytdApp = document.querySelector('ytd-app')let ytdAppData = nulllet videoDetails = nullif (!ytdApp) returnytdAppData = getYtdAppData()if ('player' in ytdAppData && 'args' in ytdAppData.player && 'raw_player_response' in ytdAppData.player.args && 'videoDetails' in ytdAppData.player.args.raw_player_response) {videoDetails = ytdAppData.player.args.raw_player_response.videoDetails} else {videoDetails = ytdAppData.playerResponse.videoDetails}const videoTitle = videoDetails.titleif (typeof videoTitle !== 'string') return// console.log(videoDetails)// console.log(videoTitle, textSlash(videoTitle))const slashVideoTitle = textSlash(`${videoTitle}\n${videoDetails.author || ''}`)let automaticHit = nullfor (const hit of hits) {const r###lt = hit.r###lt || 0const fTitle = r###lt.full_title || r###lt.title_with_featured || r###lt.titleif (!fTitle || typeof fTitle !== 'string') continueconst slashFTitle = textSlash(`${fTitle}\n${r###lt.artist_names || ''}`)let score = 0for (const key of slashFTitle.keys()) {if (slashVideoTitle.has(key)) score++}hit._matchScore = scoreif (automaticHit === null || hit._matchScore > automaticHit._matchScore) {automaticHit = hit}}// console.log(hits, automaticHit)if (automaticHit !== null) {return {hit: automaticHit}}}function main () {// do nothing}async function readPageSongInfo () {mPageSongInfoPromise = nulllet ytdAppData = getYtdAppData()if (!ytdAppData) {// the status is unknownreturn} else if (ytdAppData.page !== 'watch' || !isYtdAppReady()) {// ytdApp is initized but the pagetype or video info not exist// (youtube web app page to 'search' or 'browse')setDisableShowLyricsButton(true)genius.f.hideLyricsWithMessage()return}ytdAppData = nullmPageSongInfoPromise = new Promise(resolve => {// ytd application dataconst ytdAppData = getYtdAppData()if (!ytdAppData) return resolve({ status: -1 }) // rarely happen; only if addLyrics triggered when the page is still loadingif (ytdAppData.page !== 'watch') {// Not a video page or video page not visiblereturn resolve({ status: -2 })}// ytd video dataconst videoDetails = getVideoInfo(ytdAppData)if (!videoDetails || !videoDetails.videoId) {return resolve({ status: -3 })}const lengthSeconds = +videoDetails.lengthSecondsif (Number.isFinite(lengthSeconds)) {if (EXCLUDE_SHORT_LEN && lengthSeconds < 15) { // 15sreturn resolve({ status: -3 })}if (EXCLUDE_LIVE_VIDEO && videoDetails.isLiveContent && lengthSeconds > 1500) { // 25minreturn resolve({ status: -3 })}}getPageSongInfo(ytdAppData, videoDetails).then(pageSongInfoRes => {// pageSongInfoRes can be nullsetDisableShowLyricsButton(false)resolve({ status: 1, pageSongInfoRes, videoId: videoDetails.videoId })})})}async function actionAddLyricsOrButton () {if (lyricsDisplayState === 'loading') {// avoid iframe communcation errorreturn}if (isYouTubeLive) {setDisableShowLyricsButton(true)genius.f.hideLyricsWithMessage()return}let mPageSongInfoRes = await mPageSongInfoPromiseif (mPageSongInfoRes && mPageSongInfoRes.status === 1) {if (genius.option.autoShow) {addLyrics()} else {addLyricsButton()}}mPageSongInfoRes = null}let isTriggered = falseasync function executeMainWhenVisible (t, mPageLoadId) {if (isTriggered || mPageLoadId !== pageLoadId) returnawait new Promise(resolve => setTimeout(resolve, t)) /* eslint-disable-line no-new */if (isTriggered || mPageLoadId !== pageLoadId) returnisTriggered = trueactionAddLyricsOrButton()}function isYouTubeLiveFn () {const ytpLive = document.querySelector('.ytp-live')isYouTubeLive = ytpLive !== null && ytpLive.matches('[hidden] div.ytp-live') === false // just use simple DOM checkingconst lyricsBtn = document.querySelector('#showlyricsbutton')if (lyricsBtn !== null) lyricsBtn.classList.toggle('hide-during-ytlive', isYouTubeLive)return isYouTubeLive}let pageLoadId = 0function delayedMain () {pageLoadId++if (pageLoadId > 1e9) pageLoadId = 9if (genius && genius.current) {genius.current.compoundTitle = null}isTriggered = false// time allowed for other userscript(s) prepare the page// and also not block the pagewindow.lastFetchedQuery = null // reset search when media changedwindow.lastUserInput = nullwindow.lastUserInputConfirmed = nullwindow.defaultSongTitle = nullisYouTubeLiveFn()const mPageLoadId = pageLoadIdrequestAnimationFrame(() => {if (mPageLoadId !== pageLoadId) return// only execute in foreground tabgenius.f.hideLyricsWithMessage()readPageSongInfo()executeMainWhenVisible(200, mPageLoadId)})}function newAppHint (status) {// TODO should this be removed in favor of a README hint in the next version?if (document.getElementById('youtube-music-genius-lyrics-style')) {// Other script already runningreturn GM.setValue('newapphint', -1)}if (status % 10 === 0) {let style = document.querySelector('style#newapphint785_style')if (style === null) {style = document.createElement('style')style.id = 'newapphint785_style'style.textContent = `#newapphint785 {position:fixed;top:0%;left:0%;padding:10px;background-color:#202020;color:#bbb;font-size:large;border:2px solid red;border-radius: 5px;box-shadow: red 1px 1px 10px;transition:left 500ms, top 500ms;z-index:2500}#newapphint785 a:link, #newapphint785 a:visited {color:white;text-decoration:none}#newapphint785 a:hover {color:#b0ae10;text-decoration:none}#newapphint785 button {font-size: large;background: #555;border: 2px outset #555;margin: 3px 10px;padding: 2px;color: #eee;}#newapphint785 button:hover {border: 2px outset #fff;color: #fff;}`document.head.appendChild(style)}const container = document.createElement('div')container.id = 'newapphint785'document.body.appendChild(container)const h2 = container.appendChild(document.createElement('h2'))h2.textContent = '⚠️ Youtube Genius Lyrics 🆕'const p = container.appendChild(document.createElement('p'))p.textContent = '▶️ The "Youtube Genius Lyrics" UserScript is only applied on youtube.com. To view Genius lyrics on music.youtube.com, please install the separate UserScript, "Youtube Music Genius Lyrics".'p.appendChild(document.createElement('br'))p.appendChild(document.createElement('br'))const aSource = p.appendChild(document.createElement('a'))aSource.target = '_blank'aSource.href = 'https://greasyfork.org/en/scripts/406892-youtube-music-genius-lyrics'aSource.textContent = '📑 https://greasyfork.org/en/scripts/406892-youtube-music-genius-lyrics'p.appendChild(document.createElement('br'))p.appendChild(document.createElement('br'))const aInstall = p.appendChild(document.createElement('a'))aInstall.href = 'https://greasyfork.org/scripts/406892-youtube-music-genius-lyrics/code/Youtube%20Music%20Genius%20Lyrics.user.js'aInstall.textContent = '💘 Click to install new script'aInstall.addEventListener('click', function () {GM.setValue('newapphint', -1).then(function () {aInstall.textContent = 'ℹ️ Please reload (F5) the page after installing'})})p.appendChild(document.createElement('br'))p.appendChild(document.createElement('br'))const remindMeLater = container.appendChild(document.createElement('button'))remindMeLater.textContent = '🔜 Remind me later'remindMeLater.addEventListener('click', function () {GM.setValue('newapphint', 1).then(() => container.remove())})container.appendChild(document.createElement('br'))const doNotShowAgain = container.appendChild(document.createElement('button'))doNotShowAgain.textContent = '🆗🆒 Do not show again'doNotShowAgain.addEventListener('click', function () {GM.setValue('newapphint', -1).then(() => container.remove())})setTimeout(function () {container.style.left = `calc(50% - ${container.clientWidth / 2}px)`container.style.top = `calc(50% - ${container.clientHeight / 2}px)`}, 100)} else if (status > 0) {GM.setValue('newapphint', status + 1)}}function entryPoint () {genius = nulllet isInIframe = nulltry {isInIframe = top && window && top.constructor.name === 'Window' && window.constructor.name === 'Window' && top !== window} catch (e) { }const isRobotsTxt = document.location.href.indexOf('robots.txt') >= 0if (document.location.hostname.startsWith('music')) {if (isRobotsTxt || isInIframe) returnGM.getValue('newapphint', 0).then(function (status) {setTimeout(() => newAppHint(status), 5000)})} else {const setupMain = isRobotsTxt? function setupMain () {// do nothing}: function setupMain () {const mPageLoadId = pageLoadIdrequestAnimationFrame(() => {if (mPageLoadId !== pageLoadId) returnexecuteMainWhenVisible(600, mPageLoadId)})document.removeEventListener('yt-navigate-finish', delayedMain, false)document.addEventListener('yt-navigate-finish', delayedMain, false)isYouTubeLiveFn()}if (isInIframe && !isRobotsTxt) returnlet defaultOptions = nulltry {defaultOptions = sessionStorage[`geniusOptions_${SCRIPT_NAME}`]if (defaultOptions) defaultOptions = JSON.parse(defaultOptions)} catch (e) { }// should it be required for robots.txt as well?? can remove??genius = geniusLyrics({GM,scriptName: SCRIPT_NAME,scriptIssu###RL: 'https://github.com/cvzi/Youtube-Genius-Lyrics-userscript/issues',scriptIssuesTitle: 'Report problem: github.com/cvzi/Youtube-Genius-Lyrics-userscript/issues',domain: 'https://www.youtube.com/',emptyURL: 'https://www.youtube.com/robots.txt',main,setupMain,addCss,getHitOfElement,listSongs,showSearchField,setupLyricsDisplayDOM,addLyrics,hideLyrics,getCleanLyricsContainer,setFrameDimensions,onResize,createSpinner,customSpinnerDOM,iframeLoadedCallback1,iframeLoadedCallback2,autoSelectLyrics,defaultOptions})if (isRobotsTxt !== false || genius === null || !genius.option) returnGM.registerMenuCommand(SCRIPT_NAME + ' - Show lyrics', () => addLyrics(true))function videoTimeUpdate (ev) {if (genius.f.isScrollLyricsCallable()) {if ((ev || 0).target.nodeName === 'VIDEO') updateAutoScroll()}}document.addEventListener('genius-lyrics-actor', (ev) => {const detail = ((ev || 0).detail || 0)const action = detail.action || ''if (action === 'hideLyrics') {hideLyricsWithMessageAndStopAutoShow()} else if (action === 'showLyrics') {showLyricsButtonClicked()} else if (action === 'reloadCurrentLyrics') {genius.f.reloadCurrentLyrics()} else if (action === 'forgetCurrentLyricsSelection') {genius.f.forgetCurrentLyricsSelection()} else if (action === 'setOption' && typeof detail.prop === 'string' && 'value' in detail) {genius.option[detail.prop] = detail.value}})window.addEventListener('message', function (e) {const data = ((e || 0).data || 0)if (data.iAm === SCRIPT_NAME && data.type === 'lyricsDisplayState') {let isScrollLyricsEnabled = falseif (data.visibility !== 'loading') {const c = document.querySelector('#lyricscontainer.youtube-genius-lyrics-loading-container')if (c) {c.classList.remove('youtube-genius-lyrics-loading-container')if (data.visibility === 'loaded') {c.classList.add('youtube-genius-lyrics-found-container')document.documentElement.setAttribute('youtube-genius-lyrics-container', 'found')if (genius.current.compoundTitle) {window.lastFetchedQuery = `${genius.current.compoundTitle.replace('\t', ' ')}`}} else {document.documentElement.removeAttribute('youtube-genius-lyrics-container') // ???}}} else {window.lastFetchedQuery = null}if (data.visibility === 'loaded' && data.lyricsSuccess === true) {isScrollLyricsEnabled = genius.f.isScrollLyricsEnabled()// 'lyricsDisplayState:loaded' is after isPageAbleForAutoScroll setup in 'iframeContentRendered'if (genius.f.isScrollLyricsCallable()) {// update scroll position when the iframe is renderedupdateAutoScroll(null, true)}}lyricsDisplayState = data.visibilityif (isScrollLyricsEnabled === true) {document.addEventListener('timeupdate', videoTimeUpdate, true)} else {document.removeEventListener('timeupdate', videoTimeUpdate, true)}}})function isVideoPlaying (video) {return video.currentTime > 0 && !video.paused && !video.ended && video.readyState > video.HAVE_CURRENT_DATA}document.addEventListener('play', function (ev) {const statusCheck = () => isTriggered && lyricsDisplayState === 'hidden' && ((genius || 0).option || 0).autoShowif (!statusCheck()) returnlet video = ((ev || 0).target || 0)if (video.nodeName === 'VIDEO' && video.matches('#movie_player video[src]')) {const mPageLoadId = pageLoadIdrequestAnimationFrame(() => {if (mPageLoadId !== pageLoadId) {video = nullreturn}setTimeout(() => {if (mPageLoadId === pageLoadId) {statusCheck() && isVideoPlaying(video) && actionAddLyricsOrButton()}video = null}, 600)})}}, true)function autoscrollenabledChanged () {// when value is configurated in any tab, this function will be triggered in all tabs by Userscript ManagerrequestAnimationFrame(() => {// not execute for all foreground and background tabs, only execute when the tab is visibile / when the tab showsgenius.f.updateAutoScrollEnabled().then(() => {let isScrollLyricsEnabled = falseif (lyricsDisplayState === 'loaded') {isScrollLyricsEnabled = genius.f.isScrollLyricsEnabled()}if (isScrollLyricsEnabled === true) {document.addEventListener('timeupdate', videoTimeUpdate, true)} else {document.removeEventListener('timeupdate', videoTimeUpdate, true)}})})}if (typeof GM_addValueChangeListener === 'function') {GM_addValueChangeListener('autoscrollenabled', autoscrollenabledChanged)}function styleIframeContent () {if (genius.option.themeKey === 'genius' || genius.option.themeKey === 'geniusReact') {genius.style.enabled = truegenius.style.setup = () => {genius.style.setup = null // run once; set variables to genius.stylePropsif (genius.option.themeKey !== 'genius' && genius.option.themeKey !== 'geniusReact') return falseconst ytdApp = document.querySelector('ytd-app')if (!ytdApp) returnconst cStyle = window.getComputedStyle(ytdApp)let background = cStyle.getPropertyValue('--yt-spec-base-background')let color = cStyle.getPropertyValue('--yt-spec-text-primary')// let bbp = cStyle.getPropertyValue('--yt-spec-brand-background-primary')// let cfs = cStyle.getPropertyValue('--yt-caption-font-size')let slbc = cStyle.getPropertyValue('--ytd-searchbox-legacy-button-color')const linkColor = cStyle.getPropertyValue('--yt-spec-call-to-action') || ''const annotatedSpanBgColor = cStyle.getPropertyValue('--yt-live-chat-automod-button-background-color') || ''const annotatedSpanBgColorActive = cStyle.getPropertyValue('--yt-live-chat-automod-button-background-color-hover') || ''let fontSize = genius.option.fontSizeif (genius.option.fontSize) {fontSize = `${genius.option.fontSize}px`} else {const expander = document.querySelector('ytd-expander')const menuItem = document.querySelector('ytd-guide-entry-renderer yt-formatted-string')if (expander) {fontSize = window.getComputedStyle(expander).fontSize} else if (menuItem) {fontSize = window.getComputedStyle(menuItem).fontSize} else {fontSize = cStyle.fontSize}}if (typeof background === 'string' && typeof color === 'string' && background.length > 3 && color.length > 3) {// do nothing} else {background = nullcolor = null}if (typeof fontSize === 'string' && fontSize.length > 2) {// do nothing} else {fontSize = null}if (typeof slbc === 'string') {// do nothing} else {slbc = null}Object.assign(genius.styleProps, {'--egl-background': (background === null ? '' : `${background}`),'--egl-color': (color === null ? '' : `${color}`),'--egl-font-size': (fontSize === null ? '' : `${fontSize}`),'--egl-infobox-background': (slbc === null ? '' : `${slbc}`),'--egl-link-color': (`${linkColor}`),'--egl-annotated-span-bgcolor': (`${annotatedSpanBgColor}`),'--egl-annotated-span-bgcolor-active': (`${annotatedSpanBgColorActive}`)})return true}} else {genius.style.enabled = falsegenius.style.setup = null}}genius.onThemeChanged.push(styleIframeContent)Object.assign(genius.minimizeHit, {noImageURL: true,noFeaturedArtists: true,simpleReleaseDate: true,noRawReleaseDate: true,shortenArtistName: true,fixArtistName: true,removeStats: false,noRelatedLinks: true,onlyCompleteLyrics: false})genius.option.enableStyl###bstitution = truegenius.option.normalizeClassV2 = truegenius.option.cacheHTMLRequest = true // 1 lyrics page consume 2XX KB [OR 25 ~ 50KB under ]// prepare the shared options for the iframetry {const defaultOptions = Object.fromEntries(Object.entries(genius.option).filter(e => typeof e[1] === 'boolean'))sessionStorage[`geniusOptions_${SCRIPT_NAME}`] = JSON.stringify(defaultOptions)} catch (e) { }document.documentElement.classList.add('youtube-genius-lyrics')}}entryPoint()