Skip to content

Commit efed72e

Browse files
Add the ability to export a kodi.m3u8
1 parent 1cc0a87 commit efed72e

3 files changed

Lines changed: 67 additions & 9 deletions

File tree

background.js

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ chrome.webRequest.onBeforeSendHeaders.addListener(
4545
['requestHeaders', chrome.webRequest.OnSendHeadersOptions.EXTRA_HEADERS].filter(Boolean)
4646
);
4747

48-
async function parseClearKey(body, sendResponse, tab_url) {
48+
async function parseClearKey(body, sendResponse, tab_title, tab_url) {
4949
const clearkey = JSON.parse(atob(body));
5050

5151
const formatted_keys = clearkey["keys"].map(key => ({
@@ -66,6 +66,7 @@ async function parseClearKey(body, sendResponse, tab_url) {
6666
type: "CLEARKEY",
6767
pssh_data: pssh_data,
6868
keys: formatted_keys,
69+
title: tab_title,
6970
url: tab_url,
7071
timestamp: Math.floor(Date.now() / 1000),
7172
manifests: manifests.has(tab_url) ? manifests.get(tab_url) : []
@@ -117,7 +118,7 @@ async function generateChallenge(body, sendResponse) {
117118
sendResponse(uint8ArrayToBase64(challenge));
118119
}
119120

120-
async function parseLicense(body, sendResponse, tab_url) {
121+
async function parseLicense(body, sendResponse, tab_title, tab_url) {
121122
const license = base64toUint8Array(body);
122123
const signed_license_message = SignedMessage.decode(license);
123124

@@ -144,6 +145,7 @@ async function parseLicense(body, sendResponse, tab_url) {
144145
type: "WIDEVINE",
145146
pssh_data: pssh,
146147
keys: keys,
148+
title: tab_title,
147149
url: tab_url,
148150
timestamp: Math.floor(Date.now() / 1000),
149151
manifests: manifests.has(tab_url) ? manifests.get(tab_url) : []
@@ -196,7 +198,7 @@ async function generateChallengeRemote(body, sendResponse) {
196198
sendResponse(challenge_b64);
197199
}
198200

199-
async function parseLicenseRemote(body, sendResponse, tab_url) {
201+
async function parseLicenseRemote(body, sendResponse, tab_title, tab_url) {
200202
const license = base64toUint8Array(body);
201203
const signed_license_message = SignedMessage.decode(license);
202204

@@ -241,6 +243,7 @@ async function parseLicenseRemote(body, sendResponse, tab_url) {
241243
type: "WIDEVINE",
242244
pssh_data: session_id.pssh,
243245
keys: keys,
246+
title: tab_title,
244247
url: tab_url,
245248
timestamp: Math.floor(Date.now() / 1000),
246249
manifests: manifests.has(tab_url) ? manifests.get(tab_url) : []
@@ -254,6 +257,7 @@ async function parseLicenseRemote(body, sendResponse, tab_url) {
254257

255258
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
256259
(async () => {
260+
const tab_title = sender.tab ? sender.tab.title : '';
257261
const tab_url = sender.tab ? sender.tab.url : null;
258262

259263
switch (message.type) {
@@ -291,16 +295,16 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
291295
}
292296

293297
try {
294-
await parseClearKey(message.body, sendResponse, tab_url);
298+
await parseClearKey(message.body, sendResponse, tab_title, tab_url);
295299
return;
296300
} catch (e) {
297301
const device_type = await SettingsManager.getSelectedDeviceType();
298302
switch (device_type) {
299303
case "WVD":
300-
await parseLicense(message.body, sendResponse, tab_url);
304+
await parseLicense(message.body, sendResponse, tab_title, tab_url);
301305
break;
302306
case "REMOTE":
303-
await parseLicenseRemote(message.body, sendResponse, tab_url);
307+
await parseLicenseRemote(message.body, sendResponse, tab_title, tab_url);
304308
break;
305309
}
306310
return;

panel/panel.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
<input type="text" class="text-box" id="downloader-name">
6262
</fieldset>
6363
<button type="button" id="export">Export Logs</button>
64+
<button type="button" id="export_kodi_m3u8">Export Kodi M3U8</button>
6465
</fieldset>
6566
<fieldset>
6667
<legend>Keys</legend>

panel/panel.js

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import "../protobuf.min.js";
22
import "../license_protocol.js";
3-
import {AsyncLocalStorage, base64toUint8Array, stringToUint8Array, DeviceManager, RemoteCDMManager, SettingsManager} from "../util.js";
3+
import {AsyncLocalStorage, base64toUint8Array, DeviceManager, RemoteCDMManager, SettingsManager} from "../util.js";
44

55
const key_container = document.getElementById('key-container');
66

@@ -30,10 +30,60 @@ remote_select.addEventListener('change', async function (){
3030
}
3131
});
3232

33+
async function getLogs() {
34+
return await AsyncLocalStorage.getStorage(null);
35+
}
36+
3337
const export_button = document.getElementById('export');
3438
export_button.addEventListener('click', async function() {
35-
const logs = await AsyncLocalStorage.getStorage(null);
36-
SettingsManager.downloadFile(stringToUint8Array(JSON.stringify(logs)), "logs.json");
39+
const logs = await getLogs();
40+
SettingsManager.downloadFile(new Blob([JSON.stringify(logs)]), "logs.json");
41+
});
42+
43+
const export_kodi_m3u8_button = document.getElementById('export_kodi_m3u8');
44+
export_kodi_m3u8_button.addEventListener('click', async function () {
45+
const logs = await getLogs();
46+
47+
const getKodiDrmLegacyProp = log => "#KODIPROP:inputstream.adaptive.drm_legacy=org.w3.clearkey|" +
48+
log.keys.map(({kid, k}) => `${kid}:${k}`).join(',');
49+
50+
const getKodiDrmProp = log => {
51+
const drm = {};
52+
drm["org.w3.clearkey"] = {};
53+
drm["org.w3.clearkey"]["license"] = {};
54+
drm["org.w3.clearkey"]["license"]["keyids"] = {};
55+
log.keys.forEach(({kid, k}) => {
56+
drm["org.w3.clearkey"]["license"]["keyids"][`${kid}`] = `${k}`
57+
});
58+
return `#KODIPROP:inputstream.adaptive.drm=${JSON.stringify(drm)}`;
59+
};
60+
61+
const m3u8 = Object.values(logs)
62+
.sort((a, b) => a.title.localeCompare(b.title))
63+
.map(log => {
64+
try {
65+
const manifest = log.manifests.find(manifest => manifest.url);
66+
if (manifest) {
67+
const legacy_drm = false;
68+
return [
69+
`#EXTINF:-1,${log.title}`,
70+
'#KODIPROP:inputstream=inputstream.adaptive',
71+
'#KODIPROP:inputstream.adaptive.common_headers=' +
72+
Object.entries(manifest.headers).map(([key, value]) => `${key}=${value}`).join('&'),
73+
legacy_drm ? getKodiDrmLegacyProp(log) : getKodiDrmProp(log),
74+
manifest.url
75+
].join('\n');
76+
} else {
77+
console.warn('Skipping ' + JSON.stringify(log) + ' as it has no url');
78+
}
79+
} catch (e) {
80+
console.error('Skipping ' + JSON.stringify(log) + ' due to ' + e);
81+
}
82+
return undefined;
83+
})
84+
.filter(entry => entry)
85+
.join('\n');
86+
SettingsManager.downloadFile(new Blob([m3u8]), 'kodi.m3u8');
3787
});
3888
// ======================================
3989

@@ -145,6 +195,9 @@ async function appendLog(result) {
145195
URL:<input type="text" class="text-box" value="${result.url}">
146196
</label>
147197
<label class="expanded-only right-bound">
198+
<label class="expanded-only right-bound">
199+
Title:<input type="text" class="text-box" value="${result.title}">
200+
</label>
148201
<label class="expanded-only right-bound">
149202
PSSH:<input type="text" class="text-box" value="${result.pssh_data}">
150203
</label>

0 commit comments

Comments
 (0)