Greasy Fork is available in English.

Netflix Plus

Enable best audio and video and more features on Netflix

Инсталирай този скрипт?
Препоръчано от автора

Може да харесате и Netflix UHD.

Инсталирай този скрипт
// ==UserScript==
// @name                 Netflix Plus
// @name:ja              Netflix Plus
// @name:zh-CN           Netflix Plus
// @name:zh-TW           Netflix Plus
// @namespace            http://tampermonkey.net/
// @version              2.1
// @description          Enable best audio and video and more features on Netflix
// @description:ja       Netflixで最高の音質と画質、そしてもっと多くの機能を体験しましょう
// @description:zh-CN    在 Netflix 上开启最佳音视频质量和更多功能
// @description:zh-TW    在 Netflix 上啓用最佳影音品質和更多功能
// @author               TGSAN
// @match                https://www.netflix.com/*
// @icon                 https://www.google.com/s2/favicons?sz=64&domain=netflix.com
// @run-at               document-start
// @grant                unsafeWindow
// @grant                GM_setValue
// @grant                GM_getValue
// @grant                GM_registerMenuCommand
// @grant                GM_unregisterMenuCommand
// ==/UserScript==

(async () => {
    "use strict";

    let windowCtx = self.window;
    if (self.unsafeWindow) {
        console.log("[Netflix Plus] use unsafeWindow mode");
        windowCtx = self.unsafeWindow;
    } else {
        console.log("[Netflix Plus] use window mode (your userscript extensions not support unsafeWindow)");
    }

    // Register Netflix Plus Functions

    windowCtx._videoElementNetflixPlus;
    Object.defineProperty(windowCtx, "videoElementNetflixPlus", {
        get: function () { return windowCtx._videoElementNetflixPlus; },
        set: function (element) {
            let backup = windowCtx._videoElementNetflixPlus;
            windowCtx._videoElementNetflixPlus = element;
            element.addEventListener('playing', function () {
                if (backup === element) {
                    return;
                }

                if (!windowCtx.globalOptions.setMaxBitrate) {
                    return;
                }

                let getElementByXPath = function (xpath) {
                    return document.evaluate(
                        xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
                    ).singleNodeValue;
                };

                let selectFun = function () {
                    windowCtx.dispatchEvent(new KeyboardEvent('keydown', {
                        keyCode: 83, // S (Old)
                        ctrlKey: true,
                        altKey: true,
                        shiftKey: true,
                    }));

                    windowCtx.dispatchEvent(new KeyboardEvent('keydown', {
                        keyCode: 66, // B
                        ctrlKey: true,
                        altKey: true,
                        shiftKey: true,
                    }));

                    const VIDEO_SELECT = getElementByXPath("//div[text()='Video Bitrate / VMAF']");
                    const AUDIO_SELECT = getElementByXPath("//div[text()='Audio Bitrate']");
                    const BUTTON = getElementByXPath("//button[text()='Override']");
                    if (VIDEO_SELECT && AUDIO_SELECT && BUTTON) {
                        [VIDEO_SELECT, AUDIO_SELECT].forEach(function (el) {
                            let parent = el.parentElement;

                            let selects = parent.querySelectorAll('select');

                            selects.forEach(function (select) {
                                select.removeAttribute("disabled");
                            });

                            let options = parent.querySelectorAll('select > option');

                            for (var i = 0; i < options.length - 1; i++) {
                                options[i].removeAttribute('selected');
                            }

                            options[options.length - 1].setAttribute('selected', 'selected');
                        });

                        BUTTON.click();

                        backup = element;
                    } else {
                        setTimeout(selectFun, 100);
                    }
                }
                selectFun();
            });
        }
    });

    windowCtx.modifyFilterNetflixPlus = function (ModList, ModConfig, DRMType) {
        let DrmVersion = "playready" === DRMType ? 30 : 0;
        if (windowCtx.globalOptions.useprk) {
            ModList.push("h264mpl30-dash-playready-prk-qc");
            ModList.push("h264mpl31-dash-playready-prk-qc");
            ModList.push("h264mpl40-dash-playready-prk-qc");
        }
        if (DrmVersion == 30) {
            if (windowCtx.globalOptions.useddplus) {
                ModList.push("ddplus-2.0-dash");
                ModList.push("ddplus-5.1-dash");
                ModList.push("ddplus-5.1hq-dash");
                ModList.push("ddplus-atmos-dash");
                // ModList = ModList.filter(item => { if (!new RegExp(/heaac/g).test(JSON.stringify(item))) return item; });
            }
            if (windowCtx.globalOptions.usehevc) {
                ModList = ModList.filter(item => { if (!new RegExp(/main10-L5/g).test(JSON.stringify(item))) return item; });
            }
            if (windowCtx.globalOptions.usef12k) {
                ModList = ModList.filter(item => { if (!new RegExp(/hevc-main10-L.*-dash-cenc-prk-do/g).test(JSON.stringify(item))) return item; });
            }
            if (windowCtx.globalOptions.usef4k) {
                ModList.push("hevc-main10-L30-dash-cenc");
                ModList.push("hevc-main10-L31-dash-cenc");
                ModList.push("hevc-main10-L40-dash-cenc");
                ModList.push("hevc-main10-L41-dash-cenc");
            }
        } else {
            if (windowCtx.globalOptions.useFHD) {
                ModList.push("playready-h264mpl40-dash");
                ModList.push("playready-h264hpl40-dash");
                ModList.push("vp9-profile0-L40-dash-cenc");
                ModList.push("av1-main-L50-dash-cbcs-prk");
                ModList.push("av1-main-L51-dash-cbcs-prk");
            }
            if (windowCtx.globalOptions.useHA) {
                ModList.push("heaac-5.1-dash");
            }
            if (!windowCtx.globalOptions.usedef) {
                if (windowCtx.globalOptions.useav1) {
                    ModList.push("av1-main-L20-dash-cbcs-prk");
                    ModList.push("av1-main-L21-dash-cbcs-prk");
                    ModList = ModList.filter(item => { if (!new RegExp(/h264/g).test(JSON.stringify(item))) return item; });
                    ModList = ModList.filter(item => { if (!new RegExp(/vp9-profile/g).test(JSON.stringify(item))) return item; });
                }
                if (windowCtx.globalOptions.usevp9) {
                    ModList.push("vp9-profile0-L21-dash-cenc");
                    ModList = ModList.filter(item => { if (!new RegExp(/h264/g).test(JSON.stringify(item))) return item; });
                    ModList = ModList.filter(item => { if (!new RegExp(/av1-main/g).test(JSON.stringify(item))) return item; });
                }
                if (windowCtx.globalOptions.useAVCH) {
                    ModList = ModList.filter(item => { if (!new RegExp(/vp9-profile/g).test(JSON.stringify(item))) return item; });
                    ModList = ModList.filter(item => { if (!new RegExp(/h264mp/g).test(JSON.stringify(item))) return item; });
                    ModList = ModList.filter(item => { if (!new RegExp(/av1-main/g).test(JSON.stringify(item))) return item; });
                }
                if (windowCtx.globalOptions.useAVC) {
                    ModList = ModList.filter(item => { if (!new RegExp(/vp9-profile/g).test(JSON.stringify(item))) return item; });
                    ModList = ModList.filter(item => { if (!new RegExp(/h264hp/g).test(JSON.stringify(item))) return item; });
                    ModList = ModList.filter(item => { if (!new RegExp(/av1-main/g).test(JSON.stringify(item))) return item; });
                }
            }
        }
        if (windowCtx.globalOptions.useallSub) {
            ModConfig.showAllSubDubTracks = 1
        }
        if (windowCtx.globalOptions.closeimsc) {
            ModList = ModList.filter(item => { if (!new RegExp(/imsc1.1/g).test(JSON.stringify(item))) return item; });
        }
        return [ModList, ModConfig, DRMType];
    };

    // Main Logic

    const Event = class {
        constructor(script, target) {
            this.script = script;
            this.target = target;

            this._cancel = false;
            this._replace = null;
            this._stop = false;
        }

        preventDefault() {
            this._cancel = true;
        }
        stopPropagation() {
            this._stop = true;
        }
        replacePayload(payload) {
            this._replace = payload;
        }
    };

    let callbacks = [];
    windowCtx.addBeforeScriptExecuteListener = (f) => {
        if (typeof f !== "function") {
            throw new Error("Event handler must be a function.");
        }
        callbacks.push(f);
    };
    windowCtx.removeBeforeScriptExecuteListener = (f) => {
        let i = callbacks.length;
        while (i--) {
            if (callbacks[i] === f) {
                callbacks.splice(i, 1);
            }
        }
    };

    const dispatch = (script, target) => {
        if (script.tagName !== "SCRIPT") {
            return;
        }

        const e = new Event(script, target);

        if (typeof windowCtx.onbeforescriptexecute === "function") {
            try {
                windowCtx.onbeforescriptexecute(e);
            } catch (err) {
                console.error(err);
            }
        }

        for (const func of callbacks) {
            if (e._stop) {
                break;
            }
            try {
                func(e);
            } catch (err) {
                console.error(err);
            }
        }

        if (e._cancel) {
            script.textContent = "";
            script.remove();
        } else if (typeof e._replace === "string") {
            script.textContent = e._replace;
        }
    };
    const observer = new MutationObserver((mutations) => {
        for (const m of mutations) {
            for (const n of m.addedNodes) {
                dispatch(n, m.target);
            }
        }
    });
    observer.observe(document, {
        childList: true,
        subtree: true,
    });

    const menuItems = [
        ["setMaxBitrate", "Automatically select best bitrate available"],
        ["useallSub", "Show all audio-tracks and subs"],
        ["closeimsc", "Use SUP subtitle replace IMSC subtitle"],
        ["useDDPandHA", "Enable Dolby and HE-AAC 5.1 Audio"],
        ["useFHDAndAVCH", "Focus 1080P and High-AVC"],
    ];
    let menuCommandList = [];

    windowCtx.globalOptions = {
        useFHDAndAVCH: !checkAdvancedDrm(),
        useDDPandHA: true,
        setMaxBitrate: true,
        useallSub: true,
        get ["useddplus"]() {
            return windowCtx.globalOptions.useDDPandHA;
        },
        useAVC: false,
        get ["useFHD"]() {
            return windowCtx.globalOptions.useFHDAndAVCH;
        },
        usedef: false,
        get ["useHA"]() {
            return windowCtx.globalOptions.useDDPandHA;
        },
        get ["useAVCH"]() {
            return windowCtx.globalOptions.useFHDAndAVCH;
        },
        usevp9: false,
        useav1: false,
        useprk: true,
        usehevc: false,
        usef4k: true,
        usef12k: false,
        closeimsc: true
    };

    windowCtx.onbeforescriptexecute = function (e) {
        let scripts = document.getElementsByTagName("script");
        if (scripts.length === 0) return;
        for (let i = 0; scripts.length > i; i++) {
            let dom = scripts[i];
            if (dom.src.includes("cadmium-playercore")) {
                let playercore = document.createElement('script');
                playercore.src = "https://static-os.kumo.moe/js/netflix/cadmium-playercore.js";
                playercore.crossOrigin = dom.crossOrigin;
                playercore.async = dom.async;
                playercore.id = dom.id;
                document.head.appendChild(playercore);
                dom.remove();
                windowCtx.onbeforescriptexecute = null;
                break;
            }
        }
    };

    async function checkAdvancedDrm() {
        let supported = false;
        if (windowCtx.MSMediaKeys) {
            supported = true;
        }
        if (windowCtx.WebKitMediaKeys) {
            supported = true;
        }
        // Check L1
        let options = [
            {
                "videoCapabilities": [
                    {
                        "contentType": "video/mp4;codecs=avc1.42E01E",
                        "robustness": "HW_SECURE_ALL"
                    }
                ]
            }
        ];

        try {
            await navigator.requestMediaKeySystemAccess("com.widevine.alpha.experiment", options);
            supported = true;
        } catch { }
        console.log("Supported advanced DRM: " + supported);
        return supported;
    }

    function checkSelected(type) {
        let selected = GM_getValue("NETFLIX_PLUS_" + type);
        if (typeof selected == "boolean") {
            return selected;
        } else {
            return windowCtx.globalOptions[type];
        }
    }

    function registerSelectableVideoProcessingMenuCommand(name, type) {
        let selected = checkSelected(type);
        windowCtx.globalOptions[type] = selected;
        return GM_registerMenuCommand((checkSelected(type) ? "✅" : "🔲") + " " + name, function () {
            GM_setValue("NETFLIX_PLUS_" + type, !selected);
            windowCtx.globalOptions[type] = !selected;
            updateMenuCommand();
        });
    }

    async function updateMenuCommand() {
        for (let command of menuCommandList) {
            await GM_unregisterMenuCommand(command);
        }
        menuCommandList = [];
        for (let menuItem of menuItems) {
            menuCommandList.push(await registerSelectableVideoProcessingMenuCommand(menuItem[1], menuItem[0]));
        }
    }

    updateMenuCommand();
})();