diff --git a/extras/video-link-downloader/.gitignore b/extras/video-link-downloader/.gitignore new file mode 100644 index 000000000..d8329d5ab --- /dev/null +++ b/extras/video-link-downloader/.gitignore @@ -0,0 +1,2 @@ +*.flv +*.part* \ No newline at end of file diff --git a/extras/video-link-downloader/settings.json b/extras/video-link-downloader/settings.json new file mode 100644 index 000000000..3d9cb27da --- /dev/null +++ b/extras/video-link-downloader/settings.json @@ -0,0 +1,4 @@ +{ + "parts": 30, + "info": "test.json" +} \ No newline at end of file diff --git a/extras/video-link-downloader/test.js b/extras/video-link-downloader/test.js new file mode 100644 index 000000000..475ca91dc --- /dev/null +++ b/extras/video-link-downloader/test.js @@ -0,0 +1,7 @@ +const commandLineArgs = require("command-line-args"); + +const optionDefinitions = [ + { name: 'info', alias: 'i', defaultOption: true, type: String, defaultValue: undefined }, + { name: 'parts', alias: 'p', type: Number, defaultValue: undefined }, +]; +console.log(commandLineArgs(optionDefinitions)); diff --git a/extras/video-link-downloader/test.json b/extras/video-link-downloader/test.json index 4a2f1b7a3..be45a7107 100644 --- a/extras/video-link-downloader/test.json +++ b/extras/video-link-downloader/test.json @@ -1,6 +1,6 @@ { "urls": [ - "https://upos-hz-mirrorcosu.acgvideo.com/upgcxcode/28/51/85135128/85135128-1-64.flv?e=ig8euxZM2rNcNbh37zUVhoMznwuBhwdEto8g5X10ugNcXBlqNxHxNEVE5XREto8KqJZHUa6m5J0SqE85tZvEuENvNC8xNEVE9EKE9IMvXBvE2ENvNCImNEVEK9GVqJIwqa80WXIekXRE9IMvXBvEuENvNCImNEVEua6m2jIxux0CkF6s2JZv5x0DQJZY2F8SkXKE9IB5QK==&deadline=1555924643&gen=playurl&nbs=1&oi=1880223831&os=cosu&platform=pc&trid=45a4f29cb06f40168f60745b18d2062e&uipk=5&upsig=c8609d4926d27f696d74375112cf51bf&uparams=e,deadline,gen,nbs,oi,os,platform,trid,uipk" + "https://upos-hz-mirrorcosu.acgvideo.com/upgcxcode/28/51/85135128/85135128-1-64.flv?e=ig8euxZM2rNcNbh37zUVhoMznwuBhwdEto8g5X10ugNcXBlqNxHxNEVE5XREto8KqJZHUa6m5J0SqE85tZvEuENvNC8xNEVE9EKE9IMvXBvE2ENvNCImNEVEK9GVqJIwqa80WXIekXRE9IMvXBvEuENvNCImNEVEua6m2jIxux0CkF6s2JZv5x0DQJZY2F8SkXKE9IB5QK==&deadline=1555931845&gen=playurl&nbs=1&oi=1879749890&os=cosu&platform=pc&trid=2f286aa498ce4d24bd4020040e6997ba&uipk=5&upsig=d3498e7a0552a644dcc92b0e3be9f87a&uparams=e,deadline,gen,nbs,oi,os,platform,trid,uipk" ], "title": "【东方自作Arrange】 利己主義者のススメ 【今宵は飄逸なエゴイスト】", "totalSize": 50339950 diff --git a/extras/video-link-downloader/video-link-downloader.js b/extras/video-link-downloader/video-link-downloader.js index 21ee6ac27..208fac5ed 100644 --- a/extras/video-link-downloader/video-link-downloader.js +++ b/extras/video-link-downloader/video-link-downloader.js @@ -4,10 +4,17 @@ const commandLineArgs = require("command-line-args"); const clipboardy = require("clipboardy"); const request = require("request"); const fs = require("fs"); +const ProgressBar = require("progress"); const optionDefinitions = [ - { name: 'parts', alias: 'p', type: Number, defaultValue: 30 }, + { name: 'info', alias: 'i', defaultOption: true, type: String, defaultValue: undefined }, + { name: 'parts', alias: 'p', type: Number, defaultValue: undefined }, ]; -const options = commandLineArgs(optionDefinitions); +const commandLineOptions = commandLineArgs(optionDefinitions); +let options = commandLineOptions; +if (fs.existsSync("settings.json")) { + const jsonOptions = JSON.parse(fs.readFileSync("settings.json").toString("utf-8")); + options = Object.assign(jsonOptions, options); +} class Downloader { constructor(inputData, progress) { this.inputData = inputData; @@ -21,11 +28,14 @@ class Downloader { } cancelDownload() { [...this.progressMap.keys()].forEach(it => it.abort()); + const files = fs.readdirSync("."); + const parts = files.filter(it => it.includes(this.inputData.title)); + parts.forEach(file => fs.unlinkSync(file)); console.log("已取消下载"); } async downloadUrl(url, index = -1) { const partialLength = Math.round(this.inputData.totalSize / options.parts); - console.log(partialLength); + const title = (index === -1 ? this.inputData.title : this.inputData.title + " - " + index.toString()); let startByte = 0; let part = 0; const promises = []; @@ -59,7 +69,7 @@ class Downloader { // makeRequest(); console.error(error); }); - req.pipe(fs.createWriteStream(`${this.inputData.title}.part${part}`)); + req.pipe(fs.createWriteStream(`${title}.part${part}`)); return req; }; this.progressMap.set(makeRequest(), 0); @@ -68,43 +78,68 @@ class Downloader { part++; } await Promise.all(promises); - const dest = (index === -1 ? this.inputData.title : this.inputData.title + " - " + index.toString()) + ".flv"; - await new Promise(resolve => { + const dest = title + ".flv"; + return await new Promise(resolve => { fs.readdir(".", (_, files) => { - const parts = files.filter(it => it.includes(this.inputData.title)); - const data = parts.map(file => fs.readFileSync(file)); + const parts = files.filter(it => it.includes(title + ".part")); + const partRegex = /.*\.part([\d]+)/; + const data = parts.sort((a, b) => { + const partA = parseInt(a.replace(partRegex, "$1")); + const partB = parseInt(b.replace(partRegex, "$1")); + return partA - partB; + }).map(file => fs.readFileSync(file)); const stream = fs.createWriteStream(dest); data.forEach(it => stream.write(it)); stream.close(); parts.forEach(file => fs.unlinkSync(file)); - resolve(); + resolve(dest); }); }); } async download() { if (this.inputData.urls.length === 1) { - await this.downloadUrl(this.inputData.urls[0]); + return await this.downloadUrl(this.inputData.urls[0]); } else { - await Promise.all(this.inputData.urls.map((url, i) => this.downloadUrl(url, i))); + return await Promise.all(this.inputData.urls.map((url, i) => this.downloadUrl(url, i))); } } } (async () => { - console.log(options); - const jsonText = await clipboardy.read(); + console.log(`分段值: ${options.parts}`); + let jsonText = ''; + if (fs.existsSync(options.info)) { + jsonText = fs.readFileSync(options.info).toString("utf-8"); + } + else { + jsonText = await clipboardy.read(); + } try { const inputData = JSON.parse(jsonText); + const progressBar = new ProgressBar(":percent [:bar]", { + total: inputData.totalSize, + width: 20, + incomplete: ' ', + }); const downloader = new Downloader(inputData, progress => { - console.log((progress * 100).toFixed(2) + "%"); + progressBar.update(progress); }); process.on("SIGINT", () => { + progressBar.terminate(); downloader.cancelDownload(); process.exit(); }); - await downloader.download(); + progressBar.render(); + const result = await downloader.download(); + console.log(`下载完成: `); + if (typeof result === "string") { + console.log(result); + } + else { + result.forEach(console.log); + } } catch (error) { - console.error(`未在剪贴板找到足够的数据: ${error}`); + console.error(`错误: ${error}`); } })(); diff --git a/extras/video-link-downloader/video-link-downloader.ts b/extras/video-link-downloader/video-link-downloader.ts index 5f82ec5d5..e0256c7af 100644 --- a/extras/video-link-downloader/video-link-downloader.ts +++ b/extras/video-link-downloader/video-link-downloader.ts @@ -10,11 +10,23 @@ interface InputData title: string; totalSize: number; } +interface Settings +{ + parts: number; + info: string; +} const optionDefinitions = [ - { name: 'parts', alias: 'p', type: Number, defaultValue: 30 }, + { name: 'info', alias: 'i', defaultOption: true, type: String, defaultValue: undefined }, + { name: 'parts', alias: 'p', type: Number, defaultValue: undefined }, ]; -const options = commandLineArgs(optionDefinitions) as { parts: number }; +const commandLineOptions = commandLineArgs(optionDefinitions) as Settings; +let options = commandLineOptions; +if (fs.existsSync("settings.json")) +{ + const jsonOptions = JSON.parse(fs.readFileSync("settings.json").toString("utf-8")); + options = Object.assign(jsonOptions, options); +} type ProgressHandler = (p: number) => void; class Downloader @@ -33,11 +45,9 @@ class Downloader cancelDownload() { [...this.progressMap.keys()].forEach(it => it.abort()); - fs.readdir(".", (_, files) => - { - const parts = files.filter(it => it.includes(this.inputData.title)); - parts.forEach(file => fs.unlinkSync(file)); - }); + const files = fs.readdirSync("."); + const parts = files.filter(it => it.includes(this.inputData.title)); + parts.forEach(file => fs.unlinkSync(file)); console.log("已取消下载"); } private async downloadUrl(url: string, index: number = -1) @@ -100,7 +110,13 @@ class Downloader fs.readdir(".", (_, files) => { const parts = files.filter(it => it.includes(title + ".part")); - const data = parts.map(file => fs.readFileSync(file)); + const partRegex = /.*\.part([\d]+)/; + const data = parts.sort((a, b) => + { + const partA = parseInt(a.replace(partRegex, "$1")); + const partB = parseInt(b.replace(partRegex, "$1")); + return partA - partB; + }).map(file => fs.readFileSync(file)); const stream = fs.createWriteStream(dest); data.forEach(it => stream.write(it)); stream.close(); @@ -123,21 +139,32 @@ class Downloader } (async () => { - console.log(options); - const jsonText = await clipboardy.read(); + console.log(`分段值: ${options.parts}`); + let jsonText = ''; + if (fs.existsSync(options.info)) + { + jsonText = fs.readFileSync(options.info).toString("utf-8"); + } + else + { + jsonText = await clipboardy.read(); + } try { const inputData = JSON.parse(jsonText) as InputData; - const progressBar = new ProgressBar(":percent [:bar]", inputData.totalSize); + const progressBar = new ProgressBar(":percent [:bar]", { + total: inputData.totalSize, + width: 20, + incomplete: ' ', + }); const downloader = new Downloader(inputData, progress => { - // console.log((progress * 100).toFixed(2) + "%"); progressBar.update(progress); }); process.on("SIGINT", () => { - downloader.cancelDownload(); progressBar.terminate(); + downloader.cancelDownload(); process.exit(); }); progressBar.render();