YouTube Qualities Size

Shows file size for each quality in YouTube

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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()
})()