Skip to content

Commit

Permalink
add transcript download
Browse files Browse the repository at this point in the history
  • Loading branch information
lewisxy committed Jan 10, 2022
1 parent a8e7f2f commit 3424a34
Show file tree
Hide file tree
Showing 3 changed files with 168 additions and 48 deletions.
78 changes: 67 additions & 11 deletions background.js
Expand Up @@ -5,6 +5,8 @@ function log(...objects) {
// console.log(...objects);
}

const INTERNAL_HEADER = "internal-download";

// persistent storage for the session
// map from browser url to download info
let data = {};
Expand All @@ -16,11 +18,16 @@ let record = {};
// initialization (clear the data from previous sessions)
browser.storage.local.set({"data": data});

// similar things for transcript
let transcriptData = {};
let transcriptRecord = {};
browser.storage.local.set({"transcriptData": transcriptData});

// to make things simple, only background webrequest will commit changes
// into "data" and all other scripts just read from it.
// the data is cleared during initialization, and always in sync with "data"
// variable in memory
function saveActiveRequest(req, func) {
function saveActiveRequest(data, record, dataName, req, func) {
// only save headers and urls to save space
const savedObject = {
url: req.url,
Expand All @@ -42,7 +49,17 @@ function saveActiveRequest(req, func) {
if (Object.keys(record).length !== Object.keys(data).length) {
log("internal invarient check failed 2, something is wrong");
}
browser.storage.local.set({"data": data}, func);
let tmp = {};
tmp[dataName] = data;
browser.storage.local.set(tmp, func);
}

function saveVideoDownloadRequest(req, func) {
saveActiveRequest(data, record, "data", req, func);
}

function saveTranscriptDownloadRequest(req, func) {
saveActiveRequest(transcriptData, transcriptRecord, "transcriptData", req, func);
}

function updateUI() {
Expand All @@ -64,26 +81,65 @@ browser.webRequest.onBeforeSendHeaders.addListener(
log(details);
if (details.type === "media") {
// real request (sent from browser)
saveActiveRequest(details, function() {log("data updated")}); // don't care when will it finished.
saveVideoDownloadRequest(details, function() {log("data updated")}); // don't care when will it finished.
updateUI();
} else {
// possibly the download request
let savedReqest = record[details.url];
if (!savedReqest) {
log(`cannot found saved request for url ${details.url}`);
return;
const tmp = ArrayToObject(details.requestHeaders);
if (INTERNAL_HEADER in tmp) {
log("found internal download flag");
let savedReqest = record[details.url];
if (!savedReqest) {
log(`cannot found saved request for url ${details.url}`);
return;
}
log("replay the previous request");
// overwrite range keyword
modifyHeader({range: "bytes=0-"}, savedReqest.requestHeaders);
return {requestHeaders: savedReqest.requestHeaders};
} else {
// we are not interested in other requests
log(`ignoring request ${details.requestId}`);
}
log("replay the previous request");
// overwrite range keyword
modifyHeader({range: "bytes=0-"}, savedReqest.requestHeaders);
return {requestHeaders: savedReqest.requestHeaders};
}
// log(details);
},
{urls: ["https://ssrweb.zoom.us/*"]},
["requestHeaders"/*, "extraHeaders"*/, "blocking"]
);

// also log transcript url
browser.webRequest.onBeforeSendHeaders.addListener(
function(details) {
const url = new URL(details.url);
const params = new URLSearchParams(url.search);
if (params.get("type") === "transcript") {
const tmp = ArrayToObject(details.requestHeaders);
if (INTERNAL_HEADER in tmp) {
log("found internal download flag");
// download request, replay the record if possible
let savedReqest = transcriptRecord[details.url];
if (!savedReqest) {
log(`cannot found saved request for url ${details.url}`);
return;
}
log("replay the previous request");
return {requestHeaders: savedReqest.requestHeaders};
} else {
// normal request, record its data
log(`found transcript url ${details.url} for page ${details.originUrl}`);
saveTranscriptDownloadRequest(details, function() {log("transcript data updated")});
// updateUI();
}
} else {
// we are not interested in other requests
log(`ignoring request ${details.requestId}`);
}
},
{urls: ["https://*.zoom.us/rec/play/vtt", "https://*.zoom.us/rec/play/vtt?*"]},
["requestHeaders"/*, "extraHeaders"*/, "blocking"]
);

// modify a headers using mod
// mod is an object those key-value represents header name and content
// only 1 header in headers (if there are duplicates) will be modified
Expand Down
75 changes: 54 additions & 21 deletions popup.css
Expand Up @@ -22,8 +22,14 @@ ul {
margin: 0;
}

li>a {
display: block;
li>div.container {
/* margin: 10px; */
display: flex;
flex-flow: row wrap;
align-content: space-around;
/* flex-direction: row;
flex-wrap: wrap; */

width: 500px;

border-width: 1px;
Expand All @@ -34,36 +40,63 @@ li>a {
transition-duration: 0.5s;
}

li>a:hover {
li>div.container:hover {
background-color:rgb(235, 248, 253);
border-color: lightblue;
cursor: pointer;
}

span.download {
display: inline-block;
vertical-align: middle;
width: 15px;
height: 15px;
margin: 15px;
padding: 0px;

background: url("data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTkuMC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiIHk9IjBweCIKCSB2aWV3Qm94PSIwIDAgNDkwLjY2NyA0OTAuNjY3IiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCA0OTAuNjY3IDQ5MC42Njc7IiB4bWw6c3BhY2U9InByZXNlcnZlIj4KPHBhdGggc3R5bGU9ImZpbGw6IzIxOTZGMzsiIGQ9Ik0yNDUuMzMzLDBDMTA5LjgzOSwwLDAsMTA5LjgzOSwwLDI0NS4zMzNzMTA5LjgzOSwyNDUuMzMzLDI0NS4zMzMsMjQ1LjMzMwoJczI0NS4zMzMtMTA5LjgzOSwyNDUuMzMzLTI0NS4zMzNDNDkwLjUxNCwxMDkuOTAzLDM4MC43NjQsMC4xNTMsMjQ1LjMzMywweiIvPgo8cGF0aCBzdHlsZT0iZmlsbDojRkFGQUZBOyIgZD0iTTMxNy44NjcsMjIxLjg2N2wtMTkuMiwxOS4yVjExNy4zMzNjMC0yOS40NTUtMjMuODc4LTUzLjMzMy01My4zMzMtNTMuMzMzUzE5Miw4Ny44NzgsMTkyLDExNy4zMzMKCXYxMjMuNTg0bC0xOS4yLTE5LjJjLTE5LjEzOS0xOC4yODktNDkuMjc3LTE4LjI4OS02OC40MTYsMGMtMTguODg5LDE4Ljg5NC0xOC44ODksNDkuNTIyLDAsNjguNDE2bDExOC4yNTEsMTE4LjI1MQoJYzEyLjQ5NiwxMi40OTIsMzIuNzUyLDEyLjQ5Miw0NS4yNDgsMGwxMTguMjUxLTExOC4yNTFjMTguODg5LTE4Ljg5NCwxOC44ODktNDkuNTIyLDAtNjguNDE2CglDMzY2Ljk3NSwyMDMuNTY1LDMzNi45NDYsMjAzLjYzMSwzMTcuODY3LDIyMS44Njd6Ii8+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+Cjwvc3ZnPgo=");
background-repeat: no-repeat;
}

span.item {
display: inline-block;
vertical-align: middle;
width: 425px;
padding: 15px;
margin: 0;
span.info {
/* take entire first row */
flex: 1 80%;
margin: 10px;

overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}

li>div.container:hover>.btn {
display: block;
}

/* li>div.container:hover>.btn-disabled {
display: block;
} */

.container .btn {
display: none;
flex: 1 auto;
margin: 0px 10px 10px 10px;
padding: 5px 15px;
border: 1px solid rgb(20, 119, 212);
color: rgb(20, 119, 212);
border-radius: 4px;

text-align: center;
user-select: none;

transition-duration: 0.5s;

cursor: pointer;
}

.container .btn.disabled {
border: 1px solid gray;
color: gray;
cursor: not-allowed;
}

.container .btn:hover {
background-color: rgb(20, 119, 212);
color:white
}

.container .btn.disabled:hover {
background-color: gray;
color:white
}

#options {
margin: 15px;
margin-bottom: 30px;
Expand Down
63 changes: 47 additions & 16 deletions popup.js
Expand Up @@ -2,12 +2,14 @@

function log(...objects) {
// Uncomment this line for debugging
console.log(...objects);
// console.log(...objects);
}

// this page (script) get reloaded every time when popup is opened
let data = undefined;

let transcriptData = undefined;

getCurrentDataAndUpdateUI();
log("page loaded");

Expand All @@ -27,6 +29,11 @@ function getCurrentDataAndUpdateUI() {
log("data", d)
updateUI();
});
browser.storage.local.get("transcriptData", function(d) {
transcriptData = d.transcriptData;
log("transcriptData", d)
updateUI();
});
}

// called when download items are available or updated
Expand Down Expand Up @@ -63,40 +70,64 @@ function updateUI() {
let downloadList = document.createElement("ul");
for (let k of Object.keys(data)) {
const downloadUrl = data[k].url;
const transcriptUrl = transcriptData ? (transcriptData[k] ? transcriptData[k].url : undefined) : undefined;
const videofilename = new URL(downloadUrl).pathname.split("/").pop();

let info = document.createElement("span");
info.classList.add("item");
info.textContent = new URL(downloadUrl).pathname.split("/").pop();
info.classList.add("info");
info.textContent = videofilename;

let downloadIcon = document.createElement("span");
downloadIcon.classList.add("download");
// let downloadIcon = document.createElement("span");
// downloadIcon.classList.add("download");

let button = document.createElement("a");
button.appendChild(info);
button.appendChild(downloadIcon);
button.onclick = function() {
let videoDownloadBtn = document.createElement("span");
videoDownloadBtn.classList.add("btn");
// videoDownloadBtn.appendChild(downloadIcon);
videoDownloadBtn.onclick = function() {
download_func2(downloadUrl);
}
videoDownloadBtn.textContent = "Download Video";

let transcriptDownloadBtn = document.createElement("span");
transcriptDownloadBtn.classList.add("btn");
if (transcriptUrl) {
transcriptDownloadBtn.onclick = function() {
download_func2(transcriptUrl, `${videofilename.split(".")[0]}.vtt`);
}
transcriptDownloadBtn.textContent = "Download Transcript";
} else {
transcriptDownloadBtn.classList.add("disabled");
transcriptDownloadBtn.onclick = function() {};
transcriptDownloadBtn.textContent = "Transcript Not Available";
}

let item = document.createElement("li");
item.appendChild(button);

let container = document.createElement("div");
container.classList.add("container");
container.appendChild(info);
container.appendChild(videoDownloadBtn);
container.appendChild(transcriptDownloadBtn);
item.appendChild(container);
downloadList.appendChild(item);
}
mainFrame.appendChild(downloadList);
}

function download_func2(url) {
function download_func2(url, filename=null) {
log("initiate download", url);
const options = {
//filename: 'out.mp4',
let options = {
//filename: 'out.mp4', // this control the output filename in the dialog
url: url,
saveAs: true,
// TODO: do more experiments regarding this option
//incognito: true, // this is needed to download in private browsing mode
//headers: [],

// tag the download so that our webrequest filter will replay with the saved headers
headers: [{name: "internal-download", value: "1"}],
method: "GET"
}
if (filename) {
options["filename"] = filename;
}
browser.downloads.download(options, function(downloadId) {
log(`download started with id ${downloadId}`)
});
Expand Down

0 comments on commit 3424a34

Please sign in to comment.