mirror of
https://github.com/shirt-dev/netflix-international.git
synced 2025-05-03 08: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