Created
May 4, 2022 17:44
-
-
Save davawen/2fabba1edc49bb4ce95473fb2c805828 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const fs = require('fs'); | |
const { spawn } = require('child_process'); | |
async function sleep(time) { | |
return new Promise(resolve => setTimeout(resolve, time)); | |
} | |
async function main(index_url, concurrent_downloads, output_dir) { | |
let index = await fetch(index_url, { | |
method: 'GET', | |
headers: { | |
'Accept-Language': "en-US,en;q=0.5", | |
'Accept-Encoding': "gzip, deflate, br" | |
} | |
}).then(r => r.text()).catch(err => { | |
console.error(err); | |
console.log("Couldn't fetch index m3u8"); | |
process.exit(-1); | |
}); | |
index = index.split('\n'); | |
let video_snippets_url = []; | |
let names = []; | |
for(const line of index) { | |
try { | |
let url = new URL(line); | |
let name = url.href.split('/'); | |
name = name[name.length - 1]; | |
video_snippets_url.push(url); | |
names.push(name); | |
} | |
catch(e) { | |
// Not a snippet url | |
} | |
} | |
// video_snippets_url.reverse(); | |
/** @type {Promise<Buffer>[]} */ | |
let buffer_promises = Array(video_snippets_url.length).fill(null); | |
let output_size = 0; | |
let total_size = 0; | |
let done = 0; | |
let num_downloading = 0; | |
let interrupt = false; | |
const terminate = async (code) => { | |
console.log(`\n\nTerminating prematurly, cleaning up and dumping video buffer to ${output_dir}.`); | |
interrupt = true; | |
// All synchronous | |
for(let i = 0; i < buffer_promises.length; i++) { | |
let name = names[i]; | |
let path = `${process.argv[3]}/${name}`; | |
if(fs.existsSync(name) && fs.statSync(name).size > 1024) continue; | |
let buffer = await buffer_promises[i]; | |
if(buffer == null) continue; | |
fs.writeFileSync(path, buffer); | |
} | |
process.exit(code); | |
}; | |
console.log(); | |
video_snippets_url.forEach(async (url, idx) => { | |
let name = names[idx]; | |
let path = `${process.argv[3]}/${name}`; | |
buffer_promises[idx] = new Promise(async resolve => { | |
if(fs.existsSync(path) && fs.statSync(path).size > 1024) { // already downloaded | |
fs.readFile(path, (_, data) => resolve(data)); | |
done++; | |
return; | |
} | |
while(num_downloading >= concurrent_downloads) { | |
if(interrupt) return resolve(null); | |
await sleep(50); | |
} | |
num_downloading++; | |
total_size = Math.floor(output_size / done * video_snippets_url.length); // Extrapolate from currently downloaded | |
console.log(`\x1b[1A\x1b[1G\x1b[2KFetching ${name}... ${Math.floor(output_size / 1024)}/${Math.floor(total_size / 1024)} KiB (${Math.floor(done / video_snippets_url.length * 100)} %)`); | |
let array_buffer = new ArrayBuffer(0); | |
while(array_buffer.byteLength < 1024) { // if less than a kilobyte of data, interpret it as failure | |
if(interrupt) return resolve(null); | |
try { | |
array_buffer = await fetch(url).then(res => res.arrayBuffer()); | |
await sleep(10); // Time between retries | |
} | |
catch(err) { | |
console.error(err); | |
resolve(null); | |
terminate(-1); | |
return; | |
} | |
} | |
num_downloading--; | |
done++; | |
resolve(Buffer.from(array_buffer)); | |
output_size += array_buffer.byteLength; | |
}); | |
}); | |
process.once('SIGINT', terminate); | |
const buffers = await Promise.all(buffer_promises); | |
// Create appendable write stream | |
let stream = fs.createWriteStream("output.ts", { flags: 'a' }); | |
for(const b of buffers) { | |
stream.write(b); | |
} | |
stream.end(); | |
console.log(`\nFinished writing file! ${output_size} bytes.`); | |
console.log(`Converting to mp4.`); | |
let ffmpeg = spawn("ffmpeg", [ "-i", "-", "-acodec", "copy", "-vcodec", "copy", "output.mp4" ]); | |
ffmpeg.on("exit", (code) => { console.log(`Finished converting to mp4! (code ${code})`) }); | |
for(const b of buffers) { | |
ffmpeg.stdin.write(b); | |
} | |
ffmpeg.stdin.end(); | |
} | |
if(process.argv.length < 3 || !process.argv[2] || !process.argv[3]) { | |
console.log("Usage: node download_guya.js {m3u8 request url} {output folder} [concurrent downloads]"); | |
process.exit(-1); | |
} | |
fs.mkdirSync(process.argv[3], { recursive: true }); | |
let workers = 0; | |
if(!process.argv[4]) { | |
console.log("No or invalid count given, concurrent downloads set to 10"); | |
workers = 10; | |
} | |
else { | |
workers = parseInt(process.argv[4]); | |
if(workers == 0) workers = 10; | |
} | |
main(process.argv[2], workers, process.argv[3]); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment