mirror of
https://github.com/shirt-dev/netflix-international.git
synced 2025-05-04 00:30:33 +00:00
initial commit
This commit is contained in:
commit
a7d3a7d8fd
11 changed files with 429 additions and 0 deletions
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2021 shirt
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
7
README.md
Normal file
7
README.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# netflix-international
|
||||||
|
Extension to play Netflix with all dubs & subs, 1080p, and 5.1.
|
||||||
|
|
||||||
|
- Firefox: https://addons.mozilla.org/en-US/firefox/addon/netflix-international/
|
||||||
|
- Chrome (currently taken down): https://chrome.google.com/webstore/detail/netflix-international/pbbaoiomplacehgkfnlejmibhmbebaal
|
||||||
|
|
||||||
|
Contact me on discord: shirt#1337
|
52
background.js
Normal file
52
background.js
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
// https://stackoverflow.com/a/45985333
|
||||||
|
function getBrowser() {
|
||||||
|
if (typeof chrome !== "undefined") {
|
||||||
|
if (typeof browser !== "undefined") {
|
||||||
|
return "Firefox";
|
||||||
|
} else {
|
||||||
|
return "Chrome";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return "Edge";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
chrome.webRequest.onBeforeRequest.addListener(
|
||||||
|
function (details) {
|
||||||
|
/* Allow our shim to load an untouched copy */
|
||||||
|
if (details.url.endsWith("?no_filter")) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getBrowser() == "Chrome") {
|
||||||
|
return {
|
||||||
|
redirectUrl: chrome.extension.getURL("cadmium-playercore-shim.js")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Work around funky CORS behaviour on Firefox */
|
||||||
|
else if (getBrowser() == "Firefox") {
|
||||||
|
let filter = browser.webRequest.filterResponseData(details.requestId);
|
||||||
|
let encoder = new TextEncoder();
|
||||||
|
filter.onstop = event => {
|
||||||
|
fetch(browser.extension.getURL("cadmium-playercore-shim.js")).
|
||||||
|
then(response => response.text()).
|
||||||
|
then(text => {
|
||||||
|
filter.write(encoder.encode(text));
|
||||||
|
filter.close();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
console.error("Unsupported web browser.");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
urls: [
|
||||||
|
"*://assets.nflxext.com/*/ffe/player/html/*",
|
||||||
|
"*://www.assets.nflxext.com/*/ffe/player/html/*"
|
||||||
|
]
|
||||||
|
}, ["blocking"]
|
||||||
|
);
|
108
cadmium-playercore-shim.js
Normal file
108
cadmium-playercore-shim.js
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
/* This script runs as a drop-in replacement of the original cadmium-playercore */
|
||||||
|
console.log("Netflix International script active!");
|
||||||
|
|
||||||
|
// promisify chrome storage API for easier chaining
|
||||||
|
function chromeStorageGet(opts) {
|
||||||
|
if (getBrowser() == "Firefox") {
|
||||||
|
return chrome.storage.sync.get(opts);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
chrome.storage.sync.get(opts, resolve);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function do_patch(desc, needle, replacement) {
|
||||||
|
var match = cadmium_src.match(needle);
|
||||||
|
if (!match) {
|
||||||
|
alert("Failed to find patch: " + JSON.stringify(desc));
|
||||||
|
} else {
|
||||||
|
cadmium_src = cadmium_src.replace(needle, replacement);
|
||||||
|
console.log("[+] Patched: " + JSON.stringify(desc));
|
||||||
|
if (match[0].length < 200) { // avoid spamming the console
|
||||||
|
console.log(JSON.stringify(match[0]) + " -> " + JSON.stringify(replacement));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We need to do a synchronous request because we need to eval
|
||||||
|
the response before the body of this script finishes executing */
|
||||||
|
var request = new XMLHttpRequest();
|
||||||
|
var cadmium_url = document.getElementById("player-core-js").src;
|
||||||
|
request.open("GET", cadmium_url + "?no_filter", false); // synchronous
|
||||||
|
request.send();
|
||||||
|
|
||||||
|
var cadmium_src = request.responseText;
|
||||||
|
|
||||||
|
function get_profile_list() {
|
||||||
|
custom_profiles = [
|
||||||
|
"playready-h264mpl30-dash",
|
||||||
|
"playready-h264mpl31-dash",
|
||||||
|
"playready-h264mpl40-dash",
|
||||||
|
"playready-h264hpl30-dash",
|
||||||
|
"playready-h264hpl31-dash",
|
||||||
|
"playready-h264hpl40-dash",
|
||||||
|
"heaac-2-dash",
|
||||||
|
"heaac-2hq-dash",
|
||||||
|
"simplesdh",
|
||||||
|
"nflx-cmisc",
|
||||||
|
"BIF240",
|
||||||
|
"BIF320"
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!globalOptions.disableVP9) {
|
||||||
|
custom_profiles = custom_profiles.concat([
|
||||||
|
"vp9-profile0-L30-dash-cenc",
|
||||||
|
"vp9-profile0-L31-dash-cenc",
|
||||||
|
"vp9-profile0-L40-dash-cenc",
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (globalOptions.use6Channels) {
|
||||||
|
custom_profiles.push("heaac-5.1-dash");
|
||||||
|
}
|
||||||
|
|
||||||
|
return custom_profiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
do_patch(
|
||||||
|
"Hello world",
|
||||||
|
/(.*)/,
|
||||||
|
"console.log('Netflix International script injected!'); $1"
|
||||||
|
);
|
||||||
|
|
||||||
|
do_patch(
|
||||||
|
"Custom profiles",
|
||||||
|
/(viewableId:.,profiles:).,/,
|
||||||
|
"$1 get_profile_list(),"
|
||||||
|
);
|
||||||
|
|
||||||
|
do_patch(
|
||||||
|
"Custom profiles 2",
|
||||||
|
/(name:"default",profiles:).}/,
|
||||||
|
"$1 get_profile_list()}"
|
||||||
|
)
|
||||||
|
|
||||||
|
do_patch(
|
||||||
|
"Re-enable Ctrl+Shift+Alt+S menu",
|
||||||
|
/this\...\....\s*\&\&\s*this\.toggle\(\);/,
|
||||||
|
"this.toggle();"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (globalOptions.use6Channels) {
|
||||||
|
do_patch("Show channel indicator",
|
||||||
|
/displayName:([^\.]{1})\.([^,]{2}),/,
|
||||||
|
"displayName:$1.$2 + \" - \" + $1.channelsFormat,"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (globalOptions.showAllTracks) {
|
||||||
|
do_patch("Show all audio tracks",
|
||||||
|
/"showAllSubDubTracks",!1/,
|
||||||
|
"\"showAllSubDubTracks\",!0"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// run our patched copy of playercore
|
||||||
|
eval(cadmium_src);
|
69
content_script.js
Normal file
69
content_script.js
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
|
||||||
|
script_urls = [];
|
||||||
|
|
||||||
|
urls = [
|
||||||
|
'netflix_max_bitrate.js'
|
||||||
|
];
|
||||||
|
|
||||||
|
// https://stackoverflow.com/a/45985333
|
||||||
|
function getBrowser() {
|
||||||
|
if (typeof chrome !== "undefined") {
|
||||||
|
if (typeof browser !== "undefined") {
|
||||||
|
return "Firefox";
|
||||||
|
} else {
|
||||||
|
return "Chrome";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return "Edge";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// promisify chrome storage API for easier chaining
|
||||||
|
function chromeStorageGet(opts) {
|
||||||
|
if (getBrowser() == "Firefox") {
|
||||||
|
return chrome.storage.sync.get(opts);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
chrome.storage.sync.get(opts, resolve);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function attachScript(resp) {
|
||||||
|
let xhr = resp.target;
|
||||||
|
let mainScript = document.createElement('script');
|
||||||
|
mainScript.type = 'application/javascript';
|
||||||
|
if (xhr.status == 200) {
|
||||||
|
mainScript.text = xhr.responseText;
|
||||||
|
document.documentElement.appendChild(mainScript);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
chromeStorageGet({
|
||||||
|
use6Channels: true,
|
||||||
|
showAllTracks: true,
|
||||||
|
setMaxBitrate: false,
|
||||||
|
disableVP9: false,
|
||||||
|
}).then(items => {
|
||||||
|
// very messy workaround for accessing chrome storage outside of background / content scripts
|
||||||
|
let mainScript = document.createElement('script');
|
||||||
|
mainScript.type = 'application/javascript';
|
||||||
|
mainScript.text = `var globalOptions = JSON.parse('${JSON.stringify(items)}');`;
|
||||||
|
document.documentElement.appendChild(mainScript);
|
||||||
|
}).then(() => {
|
||||||
|
// attach and include additional scripts after we have loaded the main configuration
|
||||||
|
for (let i = 0; i < script_urls.length; i++) {
|
||||||
|
let script = document.createElement('script');
|
||||||
|
script.src = script_urls[i];
|
||||||
|
document.documentElement.appendChild(script);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < urls.length; i++) {
|
||||||
|
let mainScriptUrl = chrome.extension.getURL(urls[i]);
|
||||||
|
let xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('GET', mainScriptUrl, true);
|
||||||
|
xhr.onload = attachScript;
|
||||||
|
xhr.send();
|
||||||
|
}
|
||||||
|
});
|
BIN
img/icon128.png
Normal file
BIN
img/icon128.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 953 B |
BIN
img/icon48.png
Normal file
BIN
img/icon48.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 482 B |
51
manifest.json
Normal file
51
manifest.json
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
{
|
||||||
|
"manifest_version": 2,
|
||||||
|
"name": "Netflix International",
|
||||||
|
"description": "Displays all available Netflix audio tracks.",
|
||||||
|
"version": "2.0.0",
|
||||||
|
"author": "shirt",
|
||||||
|
"browser_action": {
|
||||||
|
"default_icon": "img/icon128.png"
|
||||||
|
},
|
||||||
|
"browser_specific_settings": {
|
||||||
|
"gecko": {
|
||||||
|
"id": "netflix-international@shirt"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"icons": {
|
||||||
|
"48": "img/icon48.png",
|
||||||
|
"128": "img/icon128.png"
|
||||||
|
},
|
||||||
|
"content_scripts": [{
|
||||||
|
"matches": [
|
||||||
|
"*://assets.nflxext.com/*/ffe/player/html/*",
|
||||||
|
"*://www.assets.nflxext.com/*/ffe/player/html/*",
|
||||||
|
"*://netflix.com/*",
|
||||||
|
"*://www.netflix.com/*"
|
||||||
|
],
|
||||||
|
"all_frames": true,
|
||||||
|
"js": ["content_script.js"],
|
||||||
|
"run_at": "document_start"
|
||||||
|
}],
|
||||||
|
"background": {
|
||||||
|
"scripts": ["background.js"]
|
||||||
|
},
|
||||||
|
"options_ui": {
|
||||||
|
"page": "pages/options.html",
|
||||||
|
"open_in_tab": false
|
||||||
|
},
|
||||||
|
"web_accessible_resources": [
|
||||||
|
"cadmium-playercore-shim.js",
|
||||||
|
"netflix_max_bitrate.js"
|
||||||
|
],
|
||||||
|
"permissions": [
|
||||||
|
"storage",
|
||||||
|
"webRequest",
|
||||||
|
"webRequestBlocking",
|
||||||
|
"*://assets.nflxext.com/*/ffe/player/html/*",
|
||||||
|
"*://www.assets.nflxext.com/*/ffe/player/html/*",
|
||||||
|
"*://netflix.com/*",
|
||||||
|
"*://www.netflix.com/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
62
netflix_max_bitrate.js
Normal file
62
netflix_max_bitrate.js
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
let getElementByXPath = function (xpath) {
|
||||||
|
return document.evaluate(
|
||||||
|
xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null
|
||||||
|
).singleNodeValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
const fn = function () {
|
||||||
|
window.dispatchEvent(new KeyboardEvent('keydown', {
|
||||||
|
keyCode: 83,
|
||||||
|
ctrlKey: true,
|
||||||
|
altKey: true,
|
||||||
|
shiftKey: true,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const VIDEO_SELECT = getElementByXPath("//div[text()='Video Bitrate']");
|
||||||
|
const AUDIO_SELECT = getElementByXPath("//div[text()='Audio Bitrate']");
|
||||||
|
const BUTTON = getElementByXPath("//button[text()='Override']");
|
||||||
|
|
||||||
|
if (!(VIDEO_SELECT && AUDIO_SELECT && BUTTON)){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
[VIDEO_SELECT, AUDIO_SELECT].forEach(function (el) {
|
||||||
|
let parent = el.parentElement;
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
let run = function () {
|
||||||
|
if (!fn()) {
|
||||||
|
setTimeout(run, 100);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const WATCH_REGEXP = /netflix.com\/watch\/.*/;
|
||||||
|
|
||||||
|
let oldLocation;
|
||||||
|
|
||||||
|
if(globalOptions.setMaxBitrate) {
|
||||||
|
console.log("netflix_max_bitrate.js enabled");
|
||||||
|
setInterval(function () {
|
||||||
|
let newLocation = window.location.toString();
|
||||||
|
|
||||||
|
if (newLocation !== oldLocation) {
|
||||||
|
oldLocation = newLocation;
|
||||||
|
if (WATCH_REGEXP.test(newLocation)) {
|
||||||
|
run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
}
|
23
pages/options.html
Normal file
23
pages/options.html
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Netflix International Options</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<input type="checkbox" id="use51"><label for="use51">Use 5.1 audio when available</label>
|
||||||
|
<br>
|
||||||
|
<input type="checkbox" id="showAllTracks"><label for="showAllTracks">Show all available audio and subtitle tracks</label>
|
||||||
|
<br>
|
||||||
|
<input type="checkbox" id="setMaxBitrate"><label for="setMaxBitrate">Automatically select best bitrate available</label>
|
||||||
|
<br>
|
||||||
|
<input type="checkbox" id="disableVP9"><label for="disableVP9">Disable VP9 codec</label>
|
||||||
|
|
||||||
|
<div id="status"></div>
|
||||||
|
<button id="save">Save</button>
|
||||||
|
|
||||||
|
<script src="options.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
36
pages/options.js
Normal file
36
pages/options.js
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
function save_options() {
|
||||||
|
const use6Channels = document.getElementById("use51").checked;
|
||||||
|
const showAllTracks = document.getElementById("showAllTracks").checked;
|
||||||
|
const setMaxBitrate = document.getElementById("setMaxBitrate").checked;
|
||||||
|
const disableVP9 = document.getElementById("disableVP9").checked;
|
||||||
|
|
||||||
|
chrome.storage.sync.set({
|
||||||
|
use6Channels,
|
||||||
|
showAllTracks,
|
||||||
|
setMaxBitrate,
|
||||||
|
disableVP9,
|
||||||
|
}, function() {
|
||||||
|
var status = document.getElementById('status');
|
||||||
|
status.textContent = 'Options saved.';
|
||||||
|
setTimeout(function() {
|
||||||
|
status.textContent = '';
|
||||||
|
}, 750);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function restore_options() {
|
||||||
|
chrome.storage.sync.get({
|
||||||
|
use6Channels: true,
|
||||||
|
showAllTracks: true,
|
||||||
|
setMaxBitrate: false,
|
||||||
|
disableVP9: false,
|
||||||
|
}, function(items) {
|
||||||
|
document.getElementById("use51").checked = items.use6Channels;
|
||||||
|
document.getElementById("showAllTracks").checked = items.showAllTracks;
|
||||||
|
document.getElementById("setMaxBitrate").checked = items.setMaxBitrate;
|
||||||
|
document.getElementById("disableVP9").checked = items.disableVP9;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', restore_options);
|
||||||
|
document.getElementById('save').addEventListener('click', save_options);
|
Loading…
Add table
Reference in a new issue