您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Shows file size for each quality in YouTube
// ==UserScript== // @name YouTube Qualities Size // @namespace ytqz.smz.k // @version 1.2.6 // @description Shows file size for each quality in YouTube // @author Abdelrahman Khalil // @match https://www.youtube.com/* // @license MIT // ==/UserScript== ;(() => { const DEFAULT_CODEC = 'vp9' const ALT_CODEC = 'avc1' const CACHE = {} // -------------------------------- // API Stuff. // -------------------------------- // NOT WORKING /*const fetchInfo = (videoId, detailpage = false) => { let url = `https://www.youtube.com/get_video_info?video_id=${videoId}&html5=1` // Retrive full info when video is copyrighted. if (detailpage) url += '&el=detailpage' return fetch(url) } // Youtube sends data as ugly ass url queries. // Parse it using URLSearchParams then get value of player_response which is stringified JSON. const parseInfo = uglyInfo => JSON.parse(getQuery(uglyInfo, 'player_response')).streamingData .adaptiveFormats */ const getFormats = async videoId => { /*let info = await fetchInfo(videoId).then(res => res.text()) if (!info.includes('adaptiveFormats')) info = await fetchInfo(videoId, true).then(res => res.text()) return parseInfo(info)*/ //return ytplayer.config.args.raw_player_response.streamingData.adaptiveFormats console.clear() console.log(ytplayer) const body = { videoId, context: { client: { clientName: 'WEB', clientVersion: ytcfg.data_.INNERTUBE_CLIENT_VERSION } } } return await fetch("https://www.youtube.com/youtubei/v1/player?key=" + ytcfg.data_.INNERTUBE_API_KEY, {method: "POST", body: JSON.stringify(body)}).then(res => res.json()).then(data => data.streamingData .adaptiveFormats) } // YouTube separates audio and video. const getAudioContentLength = formats => formats.find( f => f.audioQuality === 'AUDIO_QUALITY_MEDIUM' && f.mimeType.includes('opus') ).contentLength || 0 // Filter formats per quality. // Returns video content Length for all codecs summed by opus medium-quality audio content length. 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), {}) // -------------------------------- // DOM Stuff. // -------------------------------- const createYQSNode = mappedFormats => { let YQSElement = document.createElement('yt-quality-size') let isRTL = document.body.getAttribute('dir') === 'rtl' YQSElement.style.float = isRTL ? 'left' : 'right' YQSElement.style.textAlign = 'right' YQSElement.style.marginRight = '8px' YQSElement.setAttribute('dir', 'ltr') YQSElement.setAttribute('title', objectStringify(mappedFormats)) let textNode = document.createTextNode( mappedFormats[DEFAULT_CODEC] || mappedFormats[ALT_CODEC] || '' ) YQSElement.appendChild(textNode) return YQSElement } // Hook YQS element to each quality. const hookYQS = async addedNode => { let doesYQMExist = addedNode && addedNode.classList.contains('ytp-quality-menu'), doesYQSNotExist = !document.querySelector('yt-quality-size') if (doesYQMExist && doesYQSNotExist) { let YQM = addedNode, videoId = getQuery(location.search, 'v') if (!CACHE[videoId]) CACHE[videoId] = await getFormats(videoId) let formats = CACHE[videoId], qualitiesNode = YQM.querySelectorAll('span'), audioCL = getAudioContentLength(formats) qualitiesNode.forEach((qualityNode, index) => { if (index === qualitiesNode.length - 1) return let qualityLabel = matchQLabel(qualityNode.textContent), mappedFormats = mapFormats(formats, qualityLabel, audioCL), YQSNode = createYQSNode(mappedFormats) qualityNode.appendChild(YQSNode) }) } } // Listen to page navigation and observe settings-menu if it's /watch. const onPageUpdate = () => { let SettingsMenuElement = document.querySelector('.ytp-settings-menu') if (SettingsMenuElement) { removeEventListener('yt-page-data-updated', onPageUpdate) new MutationObserver(([{ addedNodes }]) => { hookYQS(addedNodes[0]) }).observe(SettingsMenuElement, { childList: true }) } } addEventListener('yt-page-data-updated', onPageUpdate) // -------------------------------- // Utils // -------------------------------- const getQuery = (string, query) => new URLSearchParams(string).get(query) const matchCodec = mimeType => mimeType.replace(/(?!=").+="|\..+|"/g, '') const matchQLabel = qLabel => qLabel.replace(/\s.+/, '') const toMBytes = (vCL, aCL) => { let videoAudioMB = (parseInt(vCL) + parseInt(aCL)) / 1048576 return (Math.round(videoAudioMB) || videoAudioMB.toFixed(2)) + ' MB' } const objectStringify = obj => JSON.stringify(obj, null, '') .replace(/{|}|"|,/g, '') .trim() })()
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址