您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Show estimated file size next to each YouTube quality option
// ==UserScript== // @name YouTube Quality Size (updated version of Abdelrahman Khalil's) // @namespace ahmeed.yt.qs // @version 0.1 // @description Show estimated file size next to each YouTube quality option // @author ahmeed // @match https://www.youtube.com/* // @grant none // @license MIT // ==/UserScript== ;(() => { const DEFAULT_CODEC = 'vp9' const ALT_CODEC = 'avc1' const CACHE = {} const getFormats = async videoId => { if (CACHE[videoId]) return CACHE[videoId] const body = { videoId, context: { client: { clientName: "WEB", clientVersion: "2.20250920.01.00" } } } const res = await fetch( "https://www.youtube.com/youtubei/v1/player?key=AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8", { method: "POST", headers: { "Content-Type": "application/json", "X-YouTube-Client-Name": "1", "X-YouTube-Client-Version": "2.20250920.01.00" }, body: JSON.stringify(body) } ) const data = await res.json() CACHE[videoId] = data.streamingData?.adaptiveFormats || [] return CACHE[videoId] } const getAudioContentLength = formats => formats.find( f => f.audioQuality === 'AUDIO_QUALITY_MEDIUM' && f.mimeType.includes('opus') )?.contentLength || 0 const mapFormats = (formats, qualityLabel, audioCL) => formats .filter(f => f.qualityLabel === qualityLabel && f.contentLength) .map(vf => ({ [matchCodec(vf.mimeType)]: toMBytes(vf.contentLength, audioCL) })) .reduce((r, c) => Object.assign(r, c), {}) const matchCodec = mimeType => mimeType.replace(/(?!=").+="|\..+|"/g, '') const matchQLabel = qLabel => qLabel.replace(/\s.+/, '') const toMBytes = (vCL, aCL) => { const total = (parseInt(vCL) + parseInt(aCL)) / 1048576 return total > 1024 ? (total / 1024).toFixed(2) + " GB" : total.toFixed(1) + " MB" } const objectStringify = obj => JSON.stringify(obj, null, ' ') .replace(/{|}|"|,/g, '') .trim() const createYQSNode = mappedFormats => { let YQSElement = document.createElement('yt-quality-size') YQSElement.style.marginLeft = '6px' YQSElement.style.fontSize = '0.85em' YQSElement.style.color = '#aaa' YQSElement.setAttribute('dir', 'ltr') YQSElement.setAttribute('title', objectStringify(mappedFormats)) let textNode = document.createTextNode( mappedFormats[DEFAULT_CODEC] || mappedFormats[ALT_CODEC] || '' ) YQSElement.appendChild(textNode) return YQSElement } const injectSizes = async () => { const menu = document.querySelector('.ytp-quality-menu') if (!menu || menu.querySelector('yt-quality-size')) return const videoId = new URLSearchParams(location.search).get('v') if (!videoId) return const formats = await getFormats(videoId) const audioCL = getAudioContentLength(formats) const qualitiesNode = menu.querySelectorAll('.ytp-menuitem-label') qualitiesNode.forEach((qualityNode, index) => { if (index === qualitiesNode.length - 1) return const qualityLabel = matchQLabel(qualityNode.textContent) const mappedFormats = mapFormats(formats, qualityLabel, audioCL) const YQSNode = createYQSNode(mappedFormats) qualityNode.appendChild(YQSNode) }) } // checking constantly setInterval(injectSizes, 1500) })()