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)
})()
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址