UNPKG

evdh

Version:

evdh : EisF Video Download Helper, auto download videos on web pages.

827 lines (607 loc) 17.6 kB
/* main.js, main: main js part for evdh : EisF Video Download Helper, sceext <sceext@foxmail.com> 2009EisF2015, 2015.02 * version 0.1.5.0 test201502142135 (public version) * author sceext <sceext@foxmail.com> 2015.02 * copyright 2015 sceext * * This is FREE SOFTWARE, released under GNU GPLv3+ * please see README.md and LICENSE for more information. * * evdh : EisF Video Download Helper, auto download videos with analyse service provided by flv.cn (api.flvxz.com) * Copyright (C) 2015 sceext <sceext@foxmail.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ /* require import modules */ // require modules needed by other evdh modules var fs_ = require('fs'); // b.js, dl.js, var url_ = require('url'); // b.js, var http_ = require('http'); // , dl.js, var readline_ = require('readline'); // , ui.js, var path_ = require('path'); // , main.js, aurl.js, log.js, task.js, var child_process_ = require('child_process'); // main.js var xmldom_ = require('xmldom'); // b.js, // require all other evdh modules var b_js = require('./b.js'); var conf_js = require('./conf.js'); var log_js = require('./log.js'); var dl_js = require('./dl.js'); var ui_js = require('./ui.js'); var aurl_js = require('./aurl.js'); var task_js = require('./task.js'); // exports required_modules exports.fs_ = fs_; exports.url_ = url_; exports.http_ = http_; exports.readline_ = readline_; exports.path_ = path_; exports.child_process_ = child_process_; exports.xmldom_ = xmldom_; exports.b_ = b_js; exports.conf_ = conf_js; exports.log_ = log_js; exports.dl_ = dl_js; exports.ui_ = ui_js; exports.aurl_ = aurl_js; exports.task_ = task_js; // modules needed by this module: main.js var fs = fs_; var path = path_; var child_process = child_process_; var _b = b_js; var _conf = conf_js; var _log = log_js; var _dl = dl_js; var _ui = ui_js; var _aurl = aurl_js; var _task = task_js; // main js global objects var etc = {}; var _ = {}; etc.config_file = ''; _.ci = null; // config info object _.ol = null; // global o_log log object _.dl = null; // global o_host_file_dl file download host object _.oh = null; // global o_host_task task host manage object // import time_log from log.js _log for global use function time_log(output) { _log.time_log(output); } /* functions */ // load config file function load_config_file(config_file, callback) { // finish callback(error); var _host = {}; _host.config_file = config_file; _host.callback = callback; _host.error = null; var _next = load_config_next; _next(1, _host); } function load_config_next(_step, _host) { var _next = load_config_next; switch (_step) { case 1: // first step // create o_config _host.oc = new _conf.o_config(); // set oc _host.oc.config_file = _host.config_file; _host.oc.callback = function(err){ if (err) { time_log('ERROR: load config file \'' + _host.oc.config_file + '\' failed ! '); _host.error = err; _next(-1, _host); } else { _next(_step + 1, _host); } }; // load config _host.oc.load(); break; case 2: // load config done // save config info _.ci = _host.oc.config_info; // set config to modules _conf.set_config(_.ci); time_log('INFO: use config file \"' + _host.oc.config_file + '\" '); // ok callback _next(0, _host); break; case 0: // ok finish step _host.callback(null); break; case -1: // error step _host.callback(_host.error); break; default: // step error time_log('ERROR: main.js load_config_next: step error ! '); } } // load token function load_token(token_file, callback) { // finish callback(error); var _host = {}; _host.token_file = token_file; _host.callback = callback; _host.error = null; // error info object var _next = load_token_next; _next(1, _host); } function load_token_next(_step, _host) { var _next = load_token_next; switch (_step) { case 1: // first step // get token file size _b.get_file_size(_host.token_file, function(err, size){ if (err) { time_log('ERROR: can not access token file \'' + _host.token_file + '\' ! '); _host.error = err; _next(-1, _host); } else { // save size _host.token_file_size = size; _next(_step + 1, _host); } }); break; case 2: // got token file size // check token file size if (_host.token_file_size > 256) { // more than 256 Byte, too big time_log('ERROR: token file size too big ! (' + _host.token_file_size + ' Byte) '); _host.error = true; _next(-1, _host); return; } // get token from token file // create file reader _host.fr = new _b.o_file_reader(); // set fr _host.fr.file = _host.token_file; _host.fr.callback = function(err){ if (err) { time_log('ERROR: can not read token file \'' + _host.token_file + '\' ! '); _host.error = err; _next(-1, _host); } else { _next(_step + 1, _host); } }; // read it _host.fr.load(); break; case 3: // got token from file var token = _host.fr.data.toString('utf-8'); // remove space, tab, or \n before or after token string token = _b.pure_string(token); // check token if (_b.check_char16(token) || (token.length < 8)) { // token error time_log('ERROR: token error: check token of length ' + token.length + ' from token file \'' + _host.token_file + '\' failed ! Please write a right token in token file. '); _host.error = true; _next(-1, _host); } // token ok, use it, set token to _aurl module _aurl.etc.token = token; time_log('[ OK ] load token (' + token.length + ') from \"' + _host.token_file + '\" '); // ok callback _next(0, _host); break; case 0: // ok finish step _host.callback(null); // ok callback break; case -1: // error step _host.callback(_host.error); // error callback break; default: // step error time_log('ERROR: main.js load_token_next: step error ! '); } } /* init */ function init(callback) { // init function, finish callback(error); // for evdh global main init var _host = {}; _host.callback = callback; _host.error = null; // host error info var _next = init_next; _next(1, _host); } function init_next(_step, _host) { // init next function var _next = init_next; switch (_step) { case 1: // first step // load config file load_config_file(etc.config_file, function(err){ if (err) { this.error = err; _next(-1, _host); } else { _next(_step + 1, _host); } }); break; case 2: // load token load_token(_.ci.token_file, function(err){ if (err) { this.error = err; _next(-1, _host); } else { _next(_step + 1, _host); } }); break; case 3: // create global objects _.ol = new _log.o_log(); // global log object _.dl = new _dl.o_host_file_dl(); // global file download host object _.oh = new _task.o_host_task(); // global task host manage object // set oh _.oh.dl = _.dl; // init done _next(0, _host); break; case 0: // finish step, init done // callback ok _host.callback(null); break; case -1: // init error // callback error _host.callback(_host.error); break; default: time_log('ERROR: main.js init_next: step error ! '); } } // show video info function show_video_info(info) { // out put show video info var vs = info; // video array // make same site and title video into one item var sites = {}; for (var i = 0; i < vs.length; i++) { var site = vs[i].site; if (sites[site]) { sites[site].push(vs[i]); } else { sites[site] = [vs[i]]; } } // make each site with same title video into one item for (var site in sites) { var titles = {}; var ns = sites[site]; for (var i = 0; i < ns.length; i++) { var title = ns[i].title; if (titles[title]) { titles[title].push(ns[i]); } else { titles[title] = [ns[i]]; } } // replace this site with titles sites[site] = titles; } // show each video var video_count = 1; for (var site in sites) { console.log('\nsite [' + site + '] '); var ns = sites[site]; for (var title in ns) { console.log('\n title [' + title + '] '); var nt = ns[title]; for (var i = 0; i < nt.length; i++) { var v = nt[i]; // one video show_one_video(v, video_count); video_count ++; } } } // done } function show_one_video(info, video_count) { // sum data var all_size = 0; var all_time_s = 0; var types = {}; var f = info.file; for (var i = 0; i < f.length; i++) { var nf = f[i]; // now file all_size += nf.size; all_time_s += nf.time_s; types[nf.type] = true; } // make type string var type_string = ''; for (var i in types) { type_string += (i + ' '); } // remove ' ' after type_string if (type_string.charAt(type_string.length - 1) == ' ') { type_string = type_string.slice(0, type_string.length - 1); } var fw = _ui.force_width; var get_show_file_size = _ui.get_show_file_size; var get_show_time = _ui.get_show_time; // print video info console.log('\n ' + fw(video_count, 2, true) + ': hd ' + info.hd + ' quality ' + fw('[' + info.quality + ']', 18) + fw(info.file.length, 3, true) + ' files, size ' + fw(get_show_file_size(all_size), 27, true) + ', time ' + fw(get_show_time(all_time_s), 25, true) + ', type [' + type_string + '] '); // done } // select quality function select_quality(info, callback) { // finish callback(error, info); var _host = {}; var _next = select_q_next; _host.callback = callback; _host.info = info; _host.error = null; // error info _host.ni = null; // new info, use to callback return info // use next to start _next(1, _host); } function select_q_next(_step, _host) { var _next = select_q_next; switch (_step) { case 1: // first step // get first config to use quality _host.fq = _.ci.video_quality; // make same quality to one group var qg = {}; var best_quality = 0; for (var i = 0; i < _host.info.length; i++) { var video = _host.info[i]; var quality = video.hd.toString(); // set best quality if (quality > best_quality) { best_quality = quality; } if (qg[quality]) { qg[quality].push(video); } else { qg[quality] = [video]; } } // make best quality if (_host.fq == 'best') { // auto get best quality _host.fq = best_quality.toString(); } // make sure it use string _host.fq = _host.fq.toString(); // check quality _host.now_set = qg[_host.fq]; if (_host.now_set) { // check if only one video if (_host.now_set.length > 1) { _next(_step + 1, _host); } else if (_host.now_set.length == 1) { // only one video _host.ni = _host.now_set[0]; _next(0, _host); } else { time_log('ERROR: main.js select_q_next: unknow error ! '); } } else { // no such quality _host.ni = null; _next(0, _host); } break; case 2: // more than one video, ask user to select time_log('INFO: there are ' + _host.now_set.length + ' videos of quality ' + _.ci.video_quality + ' (' + _host.fq + ') \n'); // print video info show_video_info(_host.now_set); // check auto_url mode if (_.cl_global_mode == 'auto_url') { // in 'auto_url' mode, auto select quality var vs = auto_select_quality(_host.now_set); // check result if (vs.length == 1) { // select ok console.log(''); time_log('[ OK ] in \'auto_url\' mode, do not ask user, just auto select video by quality key words in config file. \n'); _host.ni = vs[0]; _next(0, _host); // done } else { // found error console.log(''); time_log('ERROR: in \'auto_url\' mode, can not auto select video ! found ' + vs.length + ' video. \n'); _host.error = true; _next(-1, _host); } return; } // get select from user _ui.ask_line('\n Please choose one : ', function(text){ _host.input = text; _next(_step + 1, _host); }); break; case 3: // got user input var input = _host.input; var n_video = parseInt(input, 10) - 1; if ((n_video >= 0) && (n_video < _host.now_set.length)) { _host.ni = _host.now_set[n_video]; time_log('INFO: you choose video ' + (n_video + 1) + ' : '); show_video_info([_host.ni]); _next(0, _host); } else { // error input time_log('ERROR: input error, no such video ! '); _next(_step - 1, _host); } break; case 0: // finish step // ok callback _host.callback(null, _host.ni); break; case -1: // error step // error callback _host.callback(_host.error, null); break; default: // step error time_log('ERROR: main.js select_q_next: step error ! '); } } // auto_url mode, auto select quality function auto_select_quality(vs) { // get quality list var kl = _.ci.auto_qk; // key words list, auto quality keywords var nvs = vs; // new vs // use each key words for (var j = 0; j < kl.length; j++) { // get this key words var kw = kl[j]; var svs = []; // selected videos // check each rest video for (var i = 0; i < nvs.length; i++) { var v = nvs[i]; // check its quality var quality = v.quality.toString(); if (quality.indexOf(kw) != -1) { // found keywords svs.push(v); } } // check svs length if (svs.length < 2) { // should not select any more return svs; } // continue select nvs = svs; } // no more ways, use out keywords, just return it return nvs; } // refresh log function refresh_log(callback) { // finish callback(error); // get log object from o_host_file_dl var item = _.dl.get_log(); // make log object var olog = {}; olog.type = 'o_host_file_dl'; olog.json = item; // clear log item list _.ol.clear_list(); // add item _.ol.add_item(olog); // get log from _.oh item = _.oh.get_log(); olog = {}; olog.type = 'o_host_task'; olog.json = item; // add item _.ol.add_item(olog); // write it _.ol.refresh(function(err){ callback(err); }); // done } // merge video function merge_video(info, callback) { // finish callback(exit_code); // get out name and file list var tmp_path = _.ci.tmp_path; var out_name = make_out_name(info); var file_list = make_merge_file_list(info, tmp_path); var merge_tool = _.ci.merge_tool; out_name = path.join(info.base_path, out_name); var merge_args = [tmp_path, out_name].concat(file_list); // make log var merge_log = path.join(_.ci.log_path, '/merge_video.log'); var ws = fs.createWriteStream(merge_log); // create child process var cs = child_process.spawn(merge_tool, merge_args); // set output cs.stdout.on('data', function(data){ ws.write(data); }); cs.stderr.on('data', function(data){ ws.write(data); }); cs.on('close', function(code){ // end write stream ws.end(); // callback callback(code); }); } function make_merge_file_list(info, tmp_path) { var name = []; var bp = info.base_path; // base path // add each file for (var i = 0; i < info.list.length; i++) { var file = info.list[i]; var p = file.path; // file path // add to list name.push(path.relative(tmp_path, path.join(bp, p))); } // done return name; } function make_out_name(info) { var title = info.title; var tn = get_first_number_from_string(title); var name = '_' + make_num_l(tn, 4) + '_' + title + '_.mp4'; name = remove_chars(name); // done return name; } function remove_chars(text) { var t = ''; for (var i = 0; i < text.length; i++) { var c = text.charAt(i); switch (c) { case ' ': // space case ' ': // tab t += '_'; // replace space and tab with _ break; case '\'': // ' case '\"': // " break; // ignore these chars default: t += c; } } return t; } function make_num_l(num, length) { var text = num.toString(); while (text.length < length) { text = '0' + text; } return text; } function get_first_number_from_string(text) { var fi = 0; var found = false; // find first number char for (var i = 0; i < text.length; i++) { var c = text.charAt(i); if ((c >= '0') && (c <= '9')) { fi = i; found = true; break; } } // check found if (found) { var sub = text.slice(fi, text.length); return parseInt(sub, 10); } else { // not found return 0; } } /* exports */ // evdh main global objects exports.etc = etc; exports._ = _; // exports functions exports.time_log = time_log; exports.init = init; exports.show_video_info = show_video_info; exports.select_quality = select_quality; exports.merge_video = merge_video; exports.refresh_log = refresh_log; exports.remove_chars = remove_chars; exports.make_num_l = make_num_l; exports.get_first_number_from_string = get_first_number_from_string; /* end main.js */