您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Add a spectrogram to iNaturalist audio
当前为
// ==UserScript== // @name iNaturalist Spectrogram // @namespace https://greasyfork.org/users/170755 // @version 2024-01-26 // @description Add a spectrogram to iNaturalist audio // @author w_biggs // @match https://www.inaturalist.org/observations/* // @grant none // @require https://cdn.jsdelivr.net/gh/w-biggs/spectrogramJS@68733ac9f61f21dd21ec9b0f4c5727c9da8a5bb4/js/spectrogram.js // @require https://cdnjs.cloudflare.com/ajax/libs/d3/5.16.0/d3.min.js // @require https://cdn.jsdelivr.net/gh/stdlib-js/stats-base-dists-beta-cdf@48e844d06dc88c834ac95568af5207d56438297a/browser.js // @license MIT // ==/UserScript== (function() { 'use strict'; const spectrogramCSS = ` .spectrogram canvas, .spectrogram svg { position: absolute; top: 0; left: 0; } .spectrogram { position: relative; color: black; pointer-events: auto; } .axis { font: 14px sans-serif; } .axis path, .axis line { fill: none; } .axis line { shape-rendering: crispEdges; stroke: #444; stroke-width: 1.0px; stroke-dasharray: 2, 4; } #progress-line { stroke: #a50f15; stroke-width: 4px; } #ObservationShow .top_row .photos_column .PhotoBrowser .image-gallery-slide .sound-container { transform: translateY(-50%); }` const style = document.createElement('style'); style.textContent = spectrogramCSS; document.head.appendChild(style); const rgbToHex = (r, g, b) => '#' + [r, g, b].map(x => { const hex = x.toString(16) return hex.length === 1 ? '0' + hex : hex }).join(''); const a = 1; const b = 1; const threshold = 1; const granularity = 100; const colorSchemeBase = []; for (let i = 0; i <= granularity; i++) { colorSchemeBase.push(1 - ((1 / granularity) * i)); } // console.log(colorSchemeBase); const colorScheme = colorSchemeBase.map(num => Math.min(1, 1 - ((1 - num) / threshold))) .map(num => cdf(num, a, b)) .map(y => { const black = Math.round(y * 255); return rgbToHex(black, black, black); }); // console.log(`color scheme: ${colorScheme.join(', ')}`); const waitForEl = selector => { return new Promise(resolve => { if (document.querySelector(selector)) { return resolve(document.querySelector(selector)); } const observer = new MutationObserver(mutations => { if (document.querySelector(selector)) { observer.disconnect(); resolve(document.querySelector(selector)); } }); observer.observe(document.body, { childList: true, subtree: true }); }); } const specs = []; const createSpec = (soundContainer, index) => { if (soundContainer.querySelector('.sound').hidden) { soundContainer.querySelector('.sound').hidden = false; soundContainer.querySelector('.spectrogram').hidden = true; } else { const spectrogramEl = document.createElement('div'); const specId = `spec-${index}`; spectrogramEl.id = specId; spectrogramEl.classList.add('spectrogram'); const sourceEl = soundContainer.querySelector('source'); const audioUrl = sourceEl.src; soundContainer.prepend(spectrogramEl); const spec = new Spectrogram(audioUrl, `#${specId}`, { width: 480, height: 200, maxFrequency: 10000, colorScheme: colorScheme, decRange: [-100, 0], sampleSize: 256 }); specs.push(spec); soundContainer.querySelector('.sound').hidden = true; } /* for (const spec of specs) { console.log(spec.colorScheme); } */ }; waitForEl('.sound-container source').then(() => { const soundContainers = document.getElementsByClassName('sound-container'); for (let i = 0; i < soundContainers.length; i++) { const soundContainer = soundContainers[i]; const captionsBox = soundContainer.querySelector('.captions-box'); const button = document.createElement('button'); button.classList.add('btn'); button.classList.add('btn-nostyle'); button.textContent = 'Toggle spectrogram'; button.addEventListener('click', () => createSpec(soundContainer, i)); captionsBox.appendChild(button); } }); })();