Greasy Fork

Greasy Fork is available in English.

Youtube HD Premium

自动切换到你预先设定的画质。会优先使用Premium比特率。

当前为 2024-06-29 提交的版本,查看 最新版本

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

You will need to install an extension such as Tampermonkey to install this script.

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name          Youtube HD Premium
// @icon          data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAAAqhJREFUaEPtmc9rE0EUxz+DjSh6UAQRxP4F9uhBRKjipef+FwqtoZdYEk3U4jGn0FJ6KrQnj6X0EKVKKIi9tAotPZSCYilFoq0/sK1Z92V329XGENiZSRZ2LtllZ9+8z/e9ncy8UcS8qZj7TwLQ7ggmEUgiEFGB/6aQAxeBq8Al4GxonDPAydD9+dB1qkFfefy9iZ9fgRrwC/jh96v6vz+Bj8B7BduNbBwDcOA6UABuAyciCqTr9d/ACxf0oYI3YaOHAA71KfWpq8QDF6BTP27H9/GRArk+ctSBZ0BGl2SG7YwoyB4COF66lDtY+X/1EPVvKXhVTxUHKsANw6rpNl9RcFM50A1sxEj9QAiJQrcA9LvT5XPd8liy1y8Ad4GSpQF1D3NPAO4DRd2WLdlL6wUYH4dKBSYnLfmPZoDZWejrg/l5GByE5WXTIIYAxO1aDaamYGgIthsuY3TAGQQI3KtWoVCAUgkODnQ4HbZhASAYbnUV0mmYm9MJYREgcHtmxvs+1td1gLQBQNze24OxMchmYXc3CkibAOQDl6k2k4GtrZgBLC56KbSwEMXx4F2LEdjchHweJia8KVZPswCwvw+jo5DLwc6OHrePrBgGKJdhYABWVnQ7bjiF1ta8OV+WFmab5ghMT8PSEhSL3lRpvmkGSKVAct5eqwPEfkMT+y3lZeBDbDf1kq6xLqv4AL3AyxhFQUoqvQpeh2ujI+46cdjeBBJppL9Li34UBCYP5Do4ErKIeiLV82PF3UAPB64Bj4E7biW4K5JO+l6WvajUbqW8/jZsttkBxwWgB7gCnPZfCg4z5P6UH6lzTfyUgxGp7ctBRdBkBxNsjiWXv4Seyd93+DDkG/AJeKfgc6NxOvUcoOXYJQAtS2WoYxIBQ8K2bDaJQMtSGer4B8aT1sve/dr7AAAAAElFTkSuQmCC
// @author        ElectroKnight22
// @namespace     electroknight22_youtube_hd_namespace
// @version       2024.06.31.3
// @match         *://www.youtube.com/*
// @grant         GM.getValue
// @grant         GM.setValue
// @license       MIT
// @description      Automcatically switches to your pre-selected resolution. Enables premium when possible.
// @description:zh-TW   自動切換到你預先設定的畫質。會優先使用Premium位元率。
// @description:zh-CN   自动切换到你预先设定的画质。会优先使用Premium比特率。
// @description:ja      自動的に設定した画質にかわり替わります。Premiumのビットレートを優先的に選択します。

// ==/UserScript==

(function() {
    "use strict";

    // --- SETTINGS -------

    // PLEASE NOTE:
    // Settings will be saved the first time the script is loaded so that your changes aren't undone by an update.
    // If you want to make adjustments, please set "overwriteStoredSettings" to true.
    // Otherwise, your settings changes will NOT have an effect because it will use the saved settings.
    // After the script has next been run by loading a video with "overwriteStoredSettings" as true, your settings will be updated.
    // Then after that, you can set it to false again to prevent your settings from being changed by an update.

    let settings = {
        // Target Resolution to always set to. If not available, the next best resolution will be used.
        preferPremium: true,
        targetRes: "hd2160",
        // Choices for targetRes are currently:
        //   "highres" >= ( 8K / 4320p / QUHD  )
        //   "hd2880"   = ( 5K / 2880p /  UHD+ )
        //   "hd2160"   = ( 4K / 2160p /  UHD  )
        //   "hd1440"   = (      1440p /  QHD  )
        //   "hd1080"   = (      1080p /  FHD  )
        //   "hd720"    = (       720p /   HD  )
        //   "large"    = (       480p         )
        //   "medium"   = (       360p         )
        //   "small"    = (       240p         )
        //   "tiny"     = (       144p         )
        //   "auto"     = (       auto         )

        // This make it so the scripts uses the settings coded here instead of a seperate save file.
        overwriteStoredSettings: false
    };

    // --------------------
    // --- GLOBALS --------
    // --------------------

    const DEBUG = true;
    // Possible resolution choices (in decreasing order, i.e. highres is the largest):
    const resolutions = ['highres', 'hd2880', 'hd2160', 'hd1440', 'hd1080', 'hd720', 'large', 'medium', 'small', 'tiny', 'auto'];
    // YouTube has to be at least 480x270 for the player UI
    const ranks = {
        highres: "10",
        hd2880:  "9",
        hd2160:  "8",
        hd1440:  "7",
        hd1080:  "6",
        hd720:   "5",
        large:   "4",
        medium:  "3",
        small:   "2",
        tiny:    "1",
        auto:    "0"
    };

    let doc = document, win = window;

    // --------------------

    function debugLog(message) {
        if (DEBUG) {
            console.log("YTHD DEBUG | " + message);
        }
    }

    // --------------------

    // Attempt to set the video resolution to desired quality or the next best quality
    function setResolution(ytPlayer) {
        if (!ytPlayer) {
            return;
        }

        // No idea why anyone would use auto when using this script. But this handles that edge case.
        if (settings.targetRes.toLowerCase() == "auto") {
            ytPlayer.setPlaybackQuality("auto");
            return;
        }

        let limitList = ytPlayer.getAvailableQualityLevels();



        let limit = limitList[0];
        let target = settings.targetRes;

        if (ranks[target] > ranks[limit]) {
            target = limit;
        }

        if (!limitList.includes(target)) {
            for (let L of limitList) {
                if (ranks[L] < ranks[target]) {
                    target = L;
                    break;
                }
            }
        }


        let premiumLabel = ytPlayer.getAvailableQualityData().find(q => q.quality == target && q.qualityLabel.includes("Premium") && q.isPlayable)?.qualityLabel;
        let usePremium = settings.preferPremium && premiumLabel != undefined;

        debugLog(usePremium);

        // Premium quality does not have an direct API call so using emulated clicks instead
        if (usePremium) {
            let settingsButton = doc.querySelector(".ytp-settings-button:not(#ScaleBtn)");
            settingsButton.click();

            let qualityArray = ytPlayer.getAvailableQualityLabels();
            let xpathExpression = './/*[contains(text(),"' + qualityArray.join('") or contains(text(),"') + '")]/ancestor-or-self::*[@class="ytp-menuitem-content"]';
            clickQualityMenuButton();
            clickQualityButton();

            function clickQualityMenuButton() {
                let qualityMenuButton = document.evaluate(xpathExpression, ytPlayer, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
                if (qualityMenuButton) {
                    qualityMenuButton.click();
                } else {
                    setTimeout(clickQualityMenuButton, 100); // Try again after 100 milliseconds
                }
            }

            function clickQualityButton() {
                let qualityButton = document.evaluate('.//*[contains(text(),"' + premiumLabel + '") and not(@class)]/ancestor::*[@class="ytp-menuitem"]', ytPlayer, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
                if (qualityButton) {
                    qualityButton.click();
                } else {
                    setTimeout(clickQualityButton, 100); // Try again after 100 milliseconds
                }
            }

            debugLog("Resolution Set To: " + target + (usePremium ? " Premium" : ""));
            return;
        }

        ytPlayer.setPlaybackQualityRange(target);
        debugLog("Resolution Set To: " + target);
    }


    // --- MAIN -----------

    function main() {
        let ytPlayer = doc.getElementById("movie_player") || doc.getElementsByClassName("html5-video-player")[0];
        let vidId = null;

        if (ytPlayer && ytPlayer.getAvailableQualityLabels()[0]) {
            vidId = ytPlayer.getVideoData().video_id;
            setResolution(ytPlayer);
        }

        win.addEventListener("loadstart", function() {
            var ytPlayer = doc.getElementById("movie_player") || doc.getElementsByClassName("html5-video-player")[0];
            if (!ytPlayer || !ytPlayer.getAvailableQualityLabels()[0]) {
                return;
            }

            if (vidId == ytPlayer.getVideoData().video_id) {
                return;
            }

            vidId = ytPlayer.getVideoData().video_id;
            setResolution(ytPlayer);
        }, true);
    }

    async function applySettings() {
        if (typeof GM != 'undefined' && GM.getValue && GM.setValue) {
            let settingsSaved = await GM.getValue("SettingsSaved");

            if (settings.overwriteStoredSettings || !settingsSaved) {
                Object.entries(settings).forEach(([k, v]) => GM.setValue(k, v));

                await GM.setValue("SettingsSaved", true);
            } else {
                await Promise.all(
                    Object.keys(settings).map(k => { let newval = GM.getValue(k); return newval.then(v => [k, v]); })
                ).then((c) => c.forEach(([nk, nv]) => {
                    if (settings[nk] !== null && nk !== "overwriteStoredSettings") {
                        settings[nk] = nv;
                    }
                }));
            }

            debugLog(Object.entries(settings).map(([k, v]) => k + ": " + v).join(", "));
        }
    }

    applySettings().then(() => {
        main();
    });
})();