vimeo-private-downloader/index.js
nima.taheri@hootsuite.com 6ea49d2d7a feat: Remove url sanitization and let users manually figure that out
In my case changing the url actually broke the script, the original url worked just fine
2022-08-10 12:42:27 -07:00

179 lines
4.2 KiB
JavaScript

const fs = require("fs");
const url = require("url");
const https = require("https");
const log = (...args) => console.log("→", ...args);
const list = require("./videojson.js");
function loadVideo(num, cb) {
let rawMasterUrl = new URL(list[num].url);
let masterUrl = rawMasterUrl.toString();
getJson(masterUrl, num, (err, json) => {
if (err) {
return cb(err);
}
const videoData = json.video
.sort((v1, v2) => v1.avg_bitrate - v2.avg_bitrate)
.pop();
let audioData = {}
if (json.audio !== null) {
audioData = json.audio
.sort((a1, a2) => a1.avg_bitrate - a2.avg_bitrate)
.pop();
}
const videoBaseUrl = url.resolve(
url.resolve(masterUrl, json.base_url),
videoData.base_url
);
let audioBaseUrl = "";
if (json.audio !== null) {
audioBaseUrl = url.resolve(
url.resolve(masterUrl, json.base_url),
audioData.base_url
);
}
processFile(
"video",
videoBaseUrl,
videoData.init_segment,
videoData.segments,
list[num].name + ".m4v",
err => {
if (err) {
cb(err);
}
if (json.audio !== null) {
processFile(
"audio",
audioBaseUrl,
audioData.init_segment,
audioData.segments,
list[num].name + ".m4a",
err => {
if (err) {
cb(err);
}
cb(null, num + 1);
}
);
}
}
);
});
}
function processFile(type, baseUrl, initData, segments, filename, cb) {
const file = filename.replace(/[^\w.]/gi, '-');
const filePath = `./parts/${file}`;
const downloadingFlag = `./parts/.${file}~`;
if (fs.existsSync(downloadingFlag)) {
log("⚠️", ` ${file} - ${type} is incomplete, restarting the download`);
} else if (fs.existsSync(filePath)) {
log("⚠️", ` ${file} - ${type} already exists`);
cb();
} else {
fs.writeFileSync(downloadingFlag, '');
}
const segmentsUrl = segments.map(seg => {
if (!seg.url) {
throw new Error(`found a segment with an empty url: ${JSON.stringify(seg)}`);
}
return baseUrl + seg.url;
});
const initBuffer = Buffer.from(initData, "base64");
fs.writeFileSync(filePath, initBuffer);
const output = fs.createWriteStream(filePath, {
flags: "a"
});
combineSegments(type, 0, segmentsUrl, output, filePath, downloadingFlag, err => {
if (err) {
log("⚠️", ` ${err}`);
}
output.end();
cb();
});
}
function combineSegments(type, i, segmentsUrl, output, filename, downloadingFlag, cb) {
if (i >= segmentsUrl.length) {
if (fs.existsSync(downloadingFlag)) {
fs.unlinkSync(downloadingFlag);
}
log("🏁", ` ${filename} - ${type} done`);
return cb();
}
log(
"📦",
type === "video" ? "📹" : "🎧",
`Downloading ${type} segment ${i}/${segmentsUrl.length} of ${filename}`
);
let req = https
.get(segmentsUrl[i], res => {
if (res.statusCode != 200) {
cb(new Error(`Downloading segment with url '${segmentsUrl[i]}' failed with status: ${res.statusCode} ${res.statusMessage}`))
}
res.on("data", d => output.write(d));
res.on("end", () =>
combineSegments(type, i + 1, segmentsUrl, output, filename, downloadingFlag, cb)
);
})
.on("error", e => {
cb(e);
});
req.setTimeout(7000, function () {
log("⚠️", 'Timeout. Retrying');
combineSegments(type, i, segmentsUrl, output, filename, downloadingFlag, cb);
});
}
function getJson(url, n, cb) {
let data = "";
https
.get(url, res => {
if (res.statusMessage.toLowerCase() !== 'gone') {
res.on("data", d => (data += d));
res.on("end", () => cb(null, JSON.parse(data)));
} else {
return cb(`The master.json file is expired or crushed. Please update or remove it from the sequence (broken on ` + n + ` position)`);
}
})
.on("error", e => {
return cb(e);
});
}
function initJs(n = 0) {
if (!list[n] || (!list[n].name && !list[n].url)) return;
loadVideo(n, (err, num) => {
if (err) {
log("⚠️", ` ${err}`);
}
if (list[num]) {
initJs(num);
}
});
}
initJs();