4.7.0
This commit is contained in:
parent
d2383962ca
commit
6454f93d9d
9 changed files with 532 additions and 178 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -13,5 +13,7 @@ package-lock.json
|
||||||
*.ts
|
*.ts
|
||||||
*.mkv
|
*.mkv
|
||||||
*.mp4
|
*.mp4
|
||||||
|
*.ass
|
||||||
*.srt
|
*.srt
|
||||||
*.resume
|
*.resume
|
||||||
|
*.user.yml
|
||||||
|
|
|
||||||
251
funi.js
251
funi.js
|
|
@ -3,7 +3,6 @@
|
||||||
// modules build-in
|
// modules build-in
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const url = require('url');
|
|
||||||
|
|
||||||
// package json
|
// package json
|
||||||
const packageJson = require('./package.json');
|
const packageJson = require('./package.json');
|
||||||
|
|
@ -12,40 +11,26 @@ const packageJson = require('./package.json');
|
||||||
console.log(`\n=== Funimation Downloader NX ${packageJson.version} ===\n`);
|
console.log(`\n=== Funimation Downloader NX ${packageJson.version} ===\n`);
|
||||||
const api_host = 'https://prod-api-funimationnow.dadcdigital.com/api';
|
const api_host = 'https://prod-api-funimationnow.dadcdigital.com/api';
|
||||||
|
|
||||||
// request
|
|
||||||
const got = require('got');
|
|
||||||
|
|
||||||
// modules extra
|
// modules extra
|
||||||
const yaml = require('yaml');
|
const yaml = require('yaml');
|
||||||
const shlp = require('sei-helper');
|
const shlp = require('sei-helper');
|
||||||
const yargs = require('yargs');
|
const yargs = require('yargs');
|
||||||
const FormData = require('form-data');
|
|
||||||
const { lookpath } = require('lookpath');
|
const { lookpath } = require('lookpath');
|
||||||
|
|
||||||
// m3u8 and ttml
|
|
||||||
const m3u8 = require('m3u8-parsed');
|
const m3u8 = require('m3u8-parsed');
|
||||||
const streamdl = require('hls-download');
|
const streamdl = require('hls-download');
|
||||||
const { ttml2srt } = require('ttml2srt');
|
|
||||||
|
|
||||||
// get cfg file
|
// extra
|
||||||
function getYamlCfg(file){
|
const modulesFolder = __dirname + '/modules';
|
||||||
let data = {};
|
const getYamlCfg = require(modulesFolder+'/module.cfg-loader');
|
||||||
if(fs.existsSync(file)){
|
const getData = require(modulesFolder + '/module.getdata.js');
|
||||||
try{
|
const vttConvert = require(modulesFolder + '/module.vttconvert');
|
||||||
data = yaml.parse(fs.readFileSync(file, 'utf8'));
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
catch(e){}
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
// new-cfg
|
// new-cfg
|
||||||
const cfgFolder = __dirname + '/config';
|
const cfgFolder = __dirname + '/config';
|
||||||
const binCfgFile = path.join(cfgFolder,'bin-path.yml');
|
const binCfgFile = path.join(cfgFolder,'bin-path');
|
||||||
const dirCfgFile = path.join(cfgFolder,'dir-path.yml');
|
const dirCfgFile = path.join(cfgFolder,'dir-path');
|
||||||
const cliCfgFile = path.join(cfgFolder,'cli-defaults.yml');
|
const cliCfgFile = path.join(cfgFolder,'cli-defaults');
|
||||||
const tokenFile = path.join(cfgFolder,'token.yml');
|
const tokenFile = path.join(cfgFolder,'token');
|
||||||
|
|
||||||
// params
|
// params
|
||||||
let cfg = {
|
let cfg = {
|
||||||
|
|
@ -103,10 +88,10 @@ let argv = yargs
|
||||||
.boolean('nosubs')
|
.boolean('nosubs')
|
||||||
|
|
||||||
// proxy
|
// proxy
|
||||||
.describe('proxy','http(s)/socks proxy WHATWG url (ex. https://myproxyhost:1080/)')
|
// .describe('proxy','http(s)/socks proxy WHATWG url (ex. https://myproxyhost:1080/)')
|
||||||
.describe('proxy-auth','Colon-separated username and password for proxy')
|
// .describe('proxy-auth','Colon-separated username and password for proxy')
|
||||||
.describe('ssp','Ignore proxy settings for stream downloading')
|
// .describe('ssp','Ignore proxy settings for stream downloading')
|
||||||
.boolean('ssp')
|
// .boolean('ssp')
|
||||||
|
|
||||||
.describe('mp4','Mux into mp4')
|
.describe('mp4','Mux into mp4')
|
||||||
.boolean('mp4')
|
.boolean('mp4')
|
||||||
|
|
@ -153,16 +138,6 @@ let fnTitle = '',
|
||||||
stDlPath = false,
|
stDlPath = false,
|
||||||
batchDL = false;
|
batchDL = false;
|
||||||
|
|
||||||
// go to work folder
|
|
||||||
try {
|
|
||||||
fs.accessSync(cfg.dir.content, fs.R_OK | fs.W_OK);
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
console.log('[ERROR] %s',e.messsage);
|
|
||||||
process.exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
// select mode
|
// select mode
|
||||||
if(argv.auth){
|
if(argv.auth){
|
||||||
auth();
|
auth();
|
||||||
|
|
@ -170,7 +145,7 @@ if(argv.auth){
|
||||||
else if(argv.search){
|
else if(argv.search){
|
||||||
searchShow();
|
searchShow();
|
||||||
}
|
}
|
||||||
else if(argv.s && !isNaN(parseInt(argv.s,10)) && parseInt(argv.s,10) > 0){
|
else if(argv.s && !isNaN(parseInt(argv.s, 10)) && parseInt(argv.s, 10) > 0){
|
||||||
getShow();
|
getShow();
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
|
|
@ -188,15 +163,16 @@ async function auth(){
|
||||||
url: '/auth/login/',
|
url: '/auth/login/',
|
||||||
useProxy: true,
|
useProxy: true,
|
||||||
auth: authOpts,
|
auth: authOpts,
|
||||||
|
debug: argv.debug,
|
||||||
});
|
});
|
||||||
if(authData.ok){
|
if(authData.ok){
|
||||||
authData = JSON.parse(authData.res.body);
|
authData = JSON.parse(authData.res.body);
|
||||||
if(authData.token){
|
if(authData.token){
|
||||||
console.log('[INFO] Authentication success, your token: %s%s\n', authData.token.slice(0,8),'*'.repeat(32));
|
console.log('[INFO] Authentication success, your token: %s%s\n', authData.token.slice(0,8),'*'.repeat(32));
|
||||||
fs.writeFileSync(tokenFile,yaml.stringify({'token':authData.token}));
|
fs.writeFileSync(tokenFile, yaml.stringify({'token': authData.token}));
|
||||||
}
|
}
|
||||||
else if(authData.error){
|
else if(authData.error){
|
||||||
console.log('[ERROR]',authData.error,'\n');
|
console.log('[ERROR]%s\n', authData.error);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -209,8 +185,10 @@ async function searchShow(){
|
||||||
baseUrl: api_host,
|
baseUrl: api_host,
|
||||||
url: '/source/funimation/search/auto/',
|
url: '/source/funimation/search/auto/',
|
||||||
querystring: qs,
|
querystring: qs,
|
||||||
|
token: token,
|
||||||
useToken: true,
|
useToken: true,
|
||||||
useProxy: true,
|
useProxy: true,
|
||||||
|
debug: argv.debug,
|
||||||
});
|
});
|
||||||
if(!searchData.ok){return;}
|
if(!searchData.ok){return;}
|
||||||
searchData = JSON.parse(searchData.res.body);
|
searchData = JSON.parse(searchData.res.body);
|
||||||
|
|
@ -233,9 +211,11 @@ async function getShow(){
|
||||||
// show main data
|
// show main data
|
||||||
let showData = await getData({
|
let showData = await getData({
|
||||||
baseUrl: api_host,
|
baseUrl: api_host,
|
||||||
url: `/source/catalog/title/${parseInt(argv.s,10)}`,
|
url: `/source/catalog/title/${parseInt(argv.s, 10)}`,
|
||||||
|
token: token,
|
||||||
useToken: true,
|
useToken: true,
|
||||||
useProxy: true,
|
useProxy: true,
|
||||||
|
debug: argv.debug,
|
||||||
});
|
});
|
||||||
// check errors
|
// check errors
|
||||||
if(!showData.ok){return;}
|
if(!showData.ok){return;}
|
||||||
|
|
@ -257,8 +237,10 @@ async function getShow(){
|
||||||
baseUrl: api_host,
|
baseUrl: api_host,
|
||||||
url: '/funimation/episodes/',
|
url: '/funimation/episodes/',
|
||||||
querystring: qs,
|
querystring: qs,
|
||||||
|
token: token,
|
||||||
useToken: true,
|
useToken: true,
|
||||||
useProxy: true,
|
useProxy: true,
|
||||||
|
debug: argv.debug,
|
||||||
});
|
});
|
||||||
if(!episodesData.ok){return;}
|
if(!episodesData.ok){return;}
|
||||||
let eps = JSON.parse(episodesData.res.body).items, fnSlug = [], is_selected = false;
|
let eps = JSON.parse(episodesData.res.body).items, fnSlug = [], is_selected = false;
|
||||||
|
|
@ -345,8 +327,10 @@ async function getEpisode(fnSlug){
|
||||||
let episodeData = await getData({
|
let episodeData = await getData({
|
||||||
baseUrl: api_host,
|
baseUrl: api_host,
|
||||||
url: `/source/catalog/episode/${fnSlug.title}/${fnSlug.episode}/`,
|
url: `/source/catalog/episode/${fnSlug.title}/${fnSlug.episode}/`,
|
||||||
|
token: token,
|
||||||
useToken: true,
|
useToken: true,
|
||||||
useProxy: true,
|
useProxy: true,
|
||||||
|
debug: argv.debug,
|
||||||
});
|
});
|
||||||
if(!episodeData.ok){return;}
|
if(!episodeData.ok){return;}
|
||||||
let ep = JSON.parse(episodeData.res.body).items[0], streamId = 0;
|
let ep = JSON.parse(episodeData.res.body).items[0], streamId = 0;
|
||||||
|
|
@ -357,23 +341,27 @@ async function getEpisode(fnSlug){
|
||||||
ep.number = ep.number !== '' ? ep.mediaCategory+ep.number : ep.mediaCategory+'#'+ep.id;
|
ep.number = ep.number !== '' ? ep.mediaCategory+ep.number : ep.mediaCategory+'#'+ep.id;
|
||||||
}
|
}
|
||||||
fnEpNum = argv.ep && !batchDL ? ( parseInt(argv.ep, 10) < 10 ? '0' + argv.ep : argv.ep ) : ep.number;
|
fnEpNum = argv.ep && !batchDL ? ( parseInt(argv.ep, 10) < 10 ? '0' + argv.ep : argv.ep ) : ep.number;
|
||||||
|
|
||||||
// is uncut
|
// is uncut
|
||||||
let uncut = {
|
let uncut = {
|
||||||
Japanese: false,
|
Japanese: false,
|
||||||
English: false
|
English: false
|
||||||
};
|
};
|
||||||
|
|
||||||
// end
|
// end
|
||||||
console.log(
|
console.log(
|
||||||
'[INFO] %s - S%sE%s - %s',
|
'[INFO] %s - S%sE%s - %s',
|
||||||
ep.parent.title,
|
ep.parent.title,
|
||||||
(ep.parent.seasonNumber?ep.parent.seasonNumber:'?'),
|
(ep.parent.seasonNumber ? ep.parent.seasonNumber : '?'),
|
||||||
(ep.number?ep.number:'?'),
|
(ep.number ? ep.number : '?'),
|
||||||
ep.title
|
ep.title
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log('[INFO] Available streams (Non-Encrypted):');
|
console.log('[INFO] Available streams (Non-Encrypted):');
|
||||||
|
|
||||||
// map medias
|
// map medias
|
||||||
let media = ep.media.map(function(m){
|
let media = ep.media.map(function(m){
|
||||||
if(m.mediaType=='experience'){
|
if(m.mediaType == 'experience'){
|
||||||
if(m.version.match(/uncut/i)){
|
if(m.version.match(/uncut/i)){
|
||||||
uncut[m.language] = true;
|
uncut[m.language] = true;
|
||||||
}
|
}
|
||||||
|
|
@ -389,6 +377,7 @@ async function getEpisode(fnSlug){
|
||||||
return { id: 0, type: '' };
|
return { id: 0, type: '' };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// select
|
// select
|
||||||
media = media.reverse();
|
media = media.reverse();
|
||||||
for(let m of media){
|
for(let m of media){
|
||||||
|
|
@ -411,6 +400,7 @@ async function getEpisode(fnSlug){
|
||||||
console.log(`[#${m.id}] ${dub_type} [${m.version}]`,(selected?'(selected)':''));
|
console.log(`[#${m.id}] ${dub_type} [${m.version}]`,(selected?'(selected)':''));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(streamId<1){
|
if(streamId<1){
|
||||||
console.log('[ERROR] Track not selected\n');
|
console.log('[ERROR] Track not selected\n');
|
||||||
return;
|
return;
|
||||||
|
|
@ -419,9 +409,11 @@ async function getEpisode(fnSlug){
|
||||||
let streamData = await getData({
|
let streamData = await getData({
|
||||||
baseUrl: api_host,
|
baseUrl: api_host,
|
||||||
url: `/source/catalog/video/${streamId}/signed`,
|
url: `/source/catalog/video/${streamId}/signed`,
|
||||||
|
token: token,
|
||||||
|
dinstid: 'uuid',
|
||||||
useToken: true,
|
useToken: true,
|
||||||
useProxy: true,
|
useProxy: true,
|
||||||
dinstid: 'uuid',
|
debug: argv.debug,
|
||||||
});
|
});
|
||||||
if(!streamData.ok){return;}
|
if(!streamData.ok){return;}
|
||||||
streamData = JSON.parse(streamData.res.body);
|
streamData = JSON.parse(streamData.res.body);
|
||||||
|
|
@ -455,7 +447,7 @@ function getSubsUrl(m){
|
||||||
for(let i in m){
|
for(let i in m){
|
||||||
let fpp = m[i].filePath.split('.');
|
let fpp = m[i].filePath.split('.');
|
||||||
let fpe = fpp[fpp.length-1];
|
let fpe = fpp[fpp.length-1];
|
||||||
if(fpe == 'dfxp'){ // dfxp, srt, vtt
|
if(fpe == 'vtt'){ // dfxp (TTML), srt, vtt
|
||||||
return m[i].filePath;
|
return m[i].filePath;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -468,14 +460,16 @@ async function downloadStreams(){
|
||||||
let plQualityReq = await getData({
|
let plQualityReq = await getData({
|
||||||
url: tsDlPath,
|
url: tsDlPath,
|
||||||
useProxy: (argv.ssp ? false : true),
|
useProxy: (argv.ssp ? false : true),
|
||||||
|
debug: argv.debug,
|
||||||
});
|
});
|
||||||
if(!plQualityReq.ok){return;}
|
if(!plQualityReq.ok){return;}
|
||||||
|
|
||||||
let plQualityLinkList = m3u8(plQualityReq.res.body);
|
let plQualityLinkList = m3u8(plQualityReq.res.body);
|
||||||
|
|
||||||
let mainServersList = [
|
let mainServersList = [
|
||||||
|
'vmfst-api.prd.funimationsvc.com',
|
||||||
'd132fumi6di1wa.cloudfront.net',
|
'd132fumi6di1wa.cloudfront.net',
|
||||||
'funiprod.akamaized.net'
|
'funiprod.akamaized.net',
|
||||||
];
|
];
|
||||||
|
|
||||||
let plServerList = [],
|
let plServerList = [],
|
||||||
|
|
@ -486,7 +480,7 @@ async function downloadStreams(){
|
||||||
|
|
||||||
for(let s of plQualityLinkList.playlists){
|
for(let s of plQualityLinkList.playlists){
|
||||||
// set layer and max layer
|
// set layer and max layer
|
||||||
let plLayerId = parseInt(s.uri.match(/_Layer(\d+)\.m3u8$/)[1]);
|
let plLayerId = parseInt(s.uri.match(/_Layer(\d+)\.m3u8/)[1]);
|
||||||
plMaxLayer = plMaxLayer < plLayerId ? plLayerId : plMaxLayer;
|
plMaxLayer = plMaxLayer < plLayerId ? plLayerId : plMaxLayer;
|
||||||
// set urls and servers
|
// set urls and servers
|
||||||
let plUrlDl = s.uri;
|
let plUrlDl = s.uri;
|
||||||
|
|
@ -563,6 +557,7 @@ async function downloadStreams(){
|
||||||
let reqVideo = await getData({
|
let reqVideo = await getData({
|
||||||
url: videoUrl,
|
url: videoUrl,
|
||||||
useProxy: (argv.ssp ? false : true),
|
useProxy: (argv.ssp ? false : true),
|
||||||
|
debug: argv.debug,
|
||||||
});
|
});
|
||||||
if (!reqVideo.ok) { return; }
|
if (!reqVideo.ok) { return; }
|
||||||
|
|
||||||
|
|
@ -606,23 +601,29 @@ async function downloadStreams(){
|
||||||
console.log('[INFO] Skip video downloading...\n');
|
console.log('[INFO] Skip video downloading...\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// add subs
|
||||||
|
let subsUrl = stDlPath;
|
||||||
|
let subsExt = !argv.mp4 || argv.mp4 && !argv.mks && argv.ass ? '.ass' : '.srt';
|
||||||
|
let addSubs = argv.mks && subsUrl ? true : false;
|
||||||
|
|
||||||
// download subtitles
|
// download subtitles
|
||||||
if(stDlPath){
|
if(subsUrl){
|
||||||
console.log('[INFO] Downloading subtitles...');
|
console.log('[INFO] Downloading subtitles...');
|
||||||
console.log(stDlPath);
|
console.log(subsUrl);
|
||||||
let subsSrc = await getData({
|
let subsSrc = await getData({
|
||||||
url: stDlPath,
|
url: subsUrl,
|
||||||
useProxy: true,
|
useProxy: true,
|
||||||
|
debug: argv.debug,
|
||||||
});
|
});
|
||||||
if(subsSrc.ok){
|
if(subsSrc.ok){
|
||||||
let srtData = ttml2srt(subsSrc.res.body);
|
let assData = vttConvert(subsSrc.res.body, (subsExt == '.srt' ? true : false));
|
||||||
let srtFile = path.join(cfg.dir.content, fnOutput) + '.srt';
|
let assFile = path.join(cfg.dir.content, fnOutput) + subsExt;
|
||||||
fs.writeFileSync(srtFile, srtData);
|
fs.writeFileSync(assFile, assData);
|
||||||
console.log('[INFO] Subtitles downloaded!');
|
console.log('[INFO] Subtitles downloaded!');
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
console.log('[ERROR] Failed to download subtitles!');
|
console.log('[ERROR] Failed to download subtitles!');
|
||||||
argv.mks = false;
|
addSubs = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -639,13 +640,9 @@ async function downloadStreams(){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// add subs
|
|
||||||
let addSubs = argv.mks && stDlPath ? true : false;
|
|
||||||
|
|
||||||
// usage
|
// usage
|
||||||
let usableMKVmerge = true;
|
let usableMKVmerge = true;
|
||||||
let usableFFmpeg = true;
|
let usableFFmpeg = true;
|
||||||
console.log(await lookpath(path.join(cfg.bin.ffmpeg + '.exe')));
|
|
||||||
|
|
||||||
// check exec path
|
// check exec path
|
||||||
let mkvmergebinfile = await lookpath(path.join(cfg.bin.mkvmerge));
|
let mkvmergebinfile = await lookpath(path.join(cfg.bin.mkvmerge));
|
||||||
|
|
@ -678,7 +675,7 @@ async function downloadStreams(){
|
||||||
mkvmux.push(`${muxTrg}.ts`);
|
mkvmux.push(`${muxTrg}.ts`);
|
||||||
if(addSubs){
|
if(addSubs){
|
||||||
mkvmux.push('--language','0:eng');
|
mkvmux.push('--language','0:eng');
|
||||||
mkvmux.push(`${muxTrg}.srt`);
|
mkvmux.push(`${muxTrg}${subsExt}`);
|
||||||
}
|
}
|
||||||
fs.writeFileSync(`${muxTrg}.json`,JSON.stringify(mkvmux,null,' '));
|
fs.writeFileSync(`${muxTrg}.json`,JSON.stringify(mkvmux,null,' '));
|
||||||
shlp.exec('mkvmerge',`"${mkvmergebinfile}"`,`@"${muxTrg}.json"`);
|
shlp.exec('mkvmerge',`"${mkvmergebinfile}"`,`@"${muxTrg}.json"`);
|
||||||
|
|
@ -687,10 +684,10 @@ async function downloadStreams(){
|
||||||
else if(usableFFmpeg){
|
else if(usableFFmpeg){
|
||||||
let ffext = !argv.mp4 ? 'mkv' : 'mp4';
|
let ffext = !argv.mp4 ? 'mkv' : 'mp4';
|
||||||
let ffmux = `-i "${muxTrg}.ts" `;
|
let ffmux = `-i "${muxTrg}.ts" `;
|
||||||
ffmux += addSubs ? `-i "${muxTrg}.srt" ` : '';
|
ffmux += addSubs ? `-i "${muxTrg}${subsExt}" ` : '';
|
||||||
ffmux += '-map 0 -c:v copy -c:a copy ';
|
ffmux += '-map 0 -c:v copy -c:a copy ';
|
||||||
ffmux += addSubs ? '-map 1 ' : '';
|
ffmux += addSubs ? '-map 1 ' : '';
|
||||||
ffmux += addSubs && !argv.mp4 ? '-c:s srt ' : '';
|
ffmux += addSubs && !argv.mp4 ? '-c:s ass ' : '';
|
||||||
ffmux += addSubs && argv.mp4 ? '-c:s mov_text ' : '';
|
ffmux += addSubs && argv.mp4 ? '-c:s mov_text ' : '';
|
||||||
ffmux += '-metadata encoding_tool="no_variable_data" ';
|
ffmux += '-metadata encoding_tool="no_variable_data" ';
|
||||||
ffmux += `-metadata:s:v:0 title="[${argv.a}]" -metadata:s:a:0 language=${argv.sub?'jpn':'eng'} `;
|
ffmux += `-metadata:s:v:0 title="[${argv.a}]" -metadata:s:a:0 language=${argv.sub?'jpn':'eng'} `;
|
||||||
|
|
@ -708,116 +705,46 @@ async function downloadStreams(){
|
||||||
}
|
}
|
||||||
else if(argv.nocleanup){
|
else if(argv.nocleanup){
|
||||||
fs.renameSync(muxTrg+'.ts', tshTrg + '.ts');
|
fs.renameSync(muxTrg+'.ts', tshTrg + '.ts');
|
||||||
if(stDlPath && argv.mks){
|
if(subsUrl && addSubs){
|
||||||
fs.renameSync(muxTrg+'.srt', tshTrg + '.srt');
|
fs.renameSync(muxTrg +subsExt, tshTrg +subsExt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
fs.unlinkSync(muxTrg+'.ts');
|
fs.unlinkSync(muxTrg+'.ts');
|
||||||
if(stDlPath && argv.mks){
|
if(subsUrl && addSubs){
|
||||||
fs.unlinkSync(muxTrg+'.srt');
|
fs.unlinkSync(muxTrg +subsExt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log('\n[INFO] Done!\n');
|
console.log('\n[INFO] Done!\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
// get data from url
|
// make proxy URL
|
||||||
async function getData(options){
|
function buildProxy(proxyBaseUrl, proxyAuth){
|
||||||
let gOptions = {
|
if(!proxyBaseUrl.match(/^(https?|socks4|socks5):/)){
|
||||||
url: options.url,
|
proxyBaseUrl = 'http://' + proxyBaseUrl;
|
||||||
headers: {
|
|
||||||
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:70.0) Gecko/20100101 Firefox/70.0',
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if(options.baseUrl){
|
|
||||||
gOptions.prefixUrl = options.baseUrl;
|
|
||||||
gOptions.url = gOptions.url.replace(/^\//,'');
|
|
||||||
}
|
}
|
||||||
if(options.querystring){
|
|
||||||
gOptions.url += `?${new URLSearchParams(options.querystring).toString()}`;
|
|
||||||
}
|
|
||||||
if(options.auth){
|
|
||||||
gOptions.method = 'POST';
|
|
||||||
gOptions.body = new FormData();
|
|
||||||
gOptions.body.append('username', options.auth.user);
|
|
||||||
gOptions.body.append('password', options.auth.pass);
|
|
||||||
}
|
|
||||||
if(options.useToken && token){
|
|
||||||
gOptions.headers.Authorization = `Token ${token}`;
|
|
||||||
}
|
|
||||||
if(options.dinstid){
|
|
||||||
gOptions.headers.devicetype = 'Android Phone';
|
|
||||||
}
|
|
||||||
// debug
|
|
||||||
gOptions.hooks = {
|
|
||||||
beforeRequest: [
|
|
||||||
(options) => {
|
|
||||||
if(argv.debug){
|
|
||||||
console.log('[DEBUG] GOT OPTIONS:');
|
|
||||||
console.log(options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
if(options.useProxy && argv.proxy){
|
|
||||||
try{
|
|
||||||
const ProxyAgent = require('proxy-agent');
|
|
||||||
let proxyUrl = buildProxyUrl(argv.proxy,argv['proxy-auth']);
|
|
||||||
gOptions.agent = new ProxyAgent(proxyUrl);
|
|
||||||
gOptions.timeout = 10000;
|
|
||||||
}
|
|
||||||
catch(e){
|
|
||||||
console.log(`\n[WARN] Not valid proxy URL${e.input?' ('+e.input+')':''}!`);
|
|
||||||
console.log('[WARN] Skiping...');
|
|
||||||
argv.proxy = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
if(argv.debug){
|
|
||||||
console.log('[Debug] REQ:', gOptions);
|
|
||||||
}
|
|
||||||
let res = await got(gOptions);
|
|
||||||
if(res.body && res.body.match(/^</)){
|
|
||||||
throw { name: 'HTMLError', res };
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
ok: true,
|
|
||||||
res,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
catch(error){
|
|
||||||
if(argv.debug){
|
|
||||||
console.log(error);
|
|
||||||
}
|
|
||||||
if(error.response && error.response.statusCode && error.response.statusMessage){
|
|
||||||
console.log(`[ERROR] ${error.name} ${error.response.statusCode}: ${error.response.statusMessage}`);
|
|
||||||
}
|
|
||||||
else if(error.name && error.name == 'HTMLError' && error.res && error.res.body){
|
|
||||||
console.log(`[ERROR] ${error.name}:`);
|
|
||||||
console.log(error.res.body);
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
console.log(`[ERROR] ${error.name}: ${error.code||error.message}`);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
ok: false,
|
|
||||||
error,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function buildProxyUrl(proxyBaseUrl,proxyAuth){
|
|
||||||
let proxyCfg = new URL(proxyBaseUrl);
|
let proxyCfg = new URL(proxyBaseUrl);
|
||||||
if(!proxyCfg.hostname || !proxyCfg.port){
|
let proxyStr = `${proxyCfg.protocol}//`;
|
||||||
throw new Error();
|
|
||||||
|
if(typeof proxyCfg.hostname != 'string' || proxyCfg.hostname == ''){
|
||||||
|
throw new Error('[ERROR] Hostname and port required for proxy!');
|
||||||
}
|
}
|
||||||
if(proxyAuth && proxyAuth.match(':')){
|
|
||||||
proxyCfg.auth = proxyAuth;
|
if(proxyAuth && typeof proxyAuth == 'string' && proxyAuth.match(':')){
|
||||||
|
proxyCfg.username = proxyAuth.split(':')[0];
|
||||||
|
proxyCfg.password = proxyAuth.split(':')[1];
|
||||||
|
proxyStr += `${proxyCfg.username}:${proxyCfg.password}@`;
|
||||||
}
|
}
|
||||||
return url.format({
|
|
||||||
protocol: proxyCfg.protocol,
|
proxyStr += proxyCfg.hostname;
|
||||||
slashes: true,
|
|
||||||
auth: proxyCfg.auth,
|
if(!proxyCfg.port && proxyCfg.protocol == 'http:'){
|
||||||
hostname: proxyCfg.hostname,
|
proxyStr += ':80';
|
||||||
port: proxyCfg.port,
|
}
|
||||||
});
|
else if(!proxyCfg.port && proxyCfg.protocol == 'https:'){
|
||||||
|
proxyStr += ':443';
|
||||||
|
}
|
||||||
|
|
||||||
|
return proxyStr;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
BIN
modules/NotoSans-Regular.ttf
Normal file
BIN
modules/NotoSans-Regular.ttf
Normal file
Binary file not shown.
|
|
@ -7,7 +7,7 @@ const modulesCleanup = require('removeNPMAbsolutePaths');
|
||||||
const { compile } = require('nexe');
|
const { compile } = require('nexe');
|
||||||
|
|
||||||
const buildsDir = './_builds';
|
const buildsDir = './_builds';
|
||||||
const nodeVer = '-12.15.0';
|
const nodeVer = '';
|
||||||
|
|
||||||
// main
|
// main
|
||||||
(async function(){
|
(async function(){
|
||||||
|
|
@ -33,6 +33,7 @@ const nodeVer = '-12.15.0';
|
||||||
fs.mkdirSync(`${buildDir}/videos`);
|
fs.mkdirSync(`${buildDir}/videos`);
|
||||||
fs.mkdirSync(`${buildDir}/videos/_trash`);
|
fs.mkdirSync(`${buildDir}/videos/_trash`);
|
||||||
const buildConfig = {
|
const buildConfig = {
|
||||||
|
loglevel: 'verbose',
|
||||||
input: './crunchy.js',
|
input: './crunchy.js',
|
||||||
output: `${buildDir}/${pkg.short_name}`,
|
output: `${buildDir}/${pkg.short_name}`,
|
||||||
target: getTarget(buildType) + nodeVer,
|
target: getTarget(buildType) + nodeVer,
|
||||||
|
|
@ -41,23 +42,19 @@ const nodeVer = '-12.15.0';
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
console.log(`[Build] Build configuration: ${buildFull}`);
|
console.log(`[Build] Build configuration: ${buildFull}`);
|
||||||
await compile(buildConfig);
|
try {
|
||||||
if(fs.existsSync('./bin/ffmpeg')){
|
await compile(buildConfig);
|
||||||
// fs.copySync('./bin/ffmpeg', `${buildDir}/bin/ffmpeg`);
|
|
||||||
}
|
}
|
||||||
if(fs.existsSync('./bin/ffmpeg.exe')){
|
catch(e){
|
||||||
// fs.copySync('./bin/ffmpeg.exe', `${buildDir}/bin/ffmpeg.exe`);
|
console.log(e);
|
||||||
}
|
process.exit();
|
||||||
if(fs.existsSync('./bin/mkvmerge')){
|
|
||||||
// fs.copySync('./bin/mkvmerge', `${buildDir}/bin/mkvmerge`);
|
|
||||||
}
|
|
||||||
if(fs.existsSync('./bin/mkvmerge.exe')){
|
|
||||||
// fs.copySync('./bin/mkvmerge.exe', `${buildDir}/bin/mkvmerge.exe`);
|
|
||||||
}
|
}
|
||||||
|
fs.copySync('./bin/', `${buildDir}/bin/`);
|
||||||
fs.copySync('./config/bin-path.yml', `${buildDir}/config/bin-path.yml`);
|
fs.copySync('./config/bin-path.yml', `${buildDir}/config/bin-path.yml`);
|
||||||
fs.copySync('./config/cli-defaults.yml', `${buildDir}/config/cli-defaults.yml`);
|
fs.copySync('./config/cli-defaults.yml', `${buildDir}/config/cli-defaults.yml`);
|
||||||
fs.copySync('./config/dir-path.yml', `${buildDir}/config/dir-path.yml`);
|
fs.copySync('./config/dir-path.yml', `${buildDir}/config/dir-path.yml`);
|
||||||
fs.copySync('./modules/cmd-here.bat', `${buildDir}/cmd-here.bat`);
|
fs.copySync('./modules/cmd-here.bat', `${buildDir}/cmd-here.bat`);
|
||||||
|
fs.copySync('./modules/NotoSans-Regular.ttf', `${buildDir}/NotoSans-Regular.ttf`);
|
||||||
fs.copySync('./docs/', `${buildDir}/docs/`);
|
fs.copySync('./docs/', `${buildDir}/docs/`);
|
||||||
fs.copySync('./LICENSE.md', `${buildDir}/docs/LICENSE.md`);
|
fs.copySync('./LICENSE.md', `${buildDir}/docs/LICENSE.md`);
|
||||||
if(fs.existsSync(`${buildsDir}/${buildFull}.7z`)){
|
if(fs.existsSync(`${buildsDir}/${buildFull}.7z`)){
|
||||||
|
|
|
||||||
26
modules/module.cfg-loader.js
Normal file
26
modules/module.cfg-loader.js
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
const yaml = require('yaml');
|
||||||
|
const fs = require('fs');
|
||||||
|
const existsFile = fs.existsSync;
|
||||||
|
|
||||||
|
const loadYamlFile = (file) => {
|
||||||
|
return yaml.parse(fs.readFileSync(file, 'utf8'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadYamlCfg = (file) => {
|
||||||
|
if(existsFile(`${file}.user.yml`)){
|
||||||
|
file += '.user';
|
||||||
|
}
|
||||||
|
file += '.yml';
|
||||||
|
if(fs.existsSync(file)){
|
||||||
|
|
||||||
|
try{
|
||||||
|
return loadYamlFile(file, 'utf8');
|
||||||
|
}
|
||||||
|
catch(e){
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = loadYamlCfg;
|
||||||
149
modules/module.colors.json
Normal file
149
modules/module.colors.json
Normal file
|
|
@ -0,0 +1,149 @@
|
||||||
|
{
|
||||||
|
"aliceblue": "#f0f8ff",
|
||||||
|
"antiquewhite": "#faebd7",
|
||||||
|
"aqua": "#00ffff",
|
||||||
|
"aquamarine": "#7fffd4",
|
||||||
|
"azure": "#f0ffff",
|
||||||
|
"beige": "#f5f5dc",
|
||||||
|
"bisque": "#ffe4c4",
|
||||||
|
"black": "#000000",
|
||||||
|
"blanchedalmond": "#ffebcd",
|
||||||
|
"blue": "#0000ff",
|
||||||
|
"blueviolet": "#8a2be2",
|
||||||
|
"brown": "#a52a2a",
|
||||||
|
"burlywood": "#deb887",
|
||||||
|
"cadetblue": "#5f9ea0",
|
||||||
|
"chartreuse": "#7fff00",
|
||||||
|
"chocolate": "#d2691e",
|
||||||
|
"coral": "#ff7f50",
|
||||||
|
"cornflowerblue": "#6495ed",
|
||||||
|
"cornsilk": "#fff8dc",
|
||||||
|
"crimson": "#dc143c",
|
||||||
|
"cyan": "#00ffff",
|
||||||
|
"darkblue": "#00008b",
|
||||||
|
"darkcyan": "#008b8b",
|
||||||
|
"darkgoldenrod": "#b8860b",
|
||||||
|
"darkgray": "#a9a9a9",
|
||||||
|
"darkgreen": "#006400",
|
||||||
|
"darkgrey": "#a9a9a9",
|
||||||
|
"darkkhaki": "#bdb76b",
|
||||||
|
"darkmagenta": "#8b008b",
|
||||||
|
"darkolivegreen": "#556b2f",
|
||||||
|
"darkorange": "#ff8c00",
|
||||||
|
"darkorchid": "#9932cc",
|
||||||
|
"darkred": "#8b0000",
|
||||||
|
"darksalmon": "#e9967a",
|
||||||
|
"darkseagreen": "#8fbc8f",
|
||||||
|
"darkslateblue": "#483d8b",
|
||||||
|
"darkslategray": "#2f4f4f",
|
||||||
|
"darkslategrey": "#2f4f4f",
|
||||||
|
"darkturquoise": "#00ced1",
|
||||||
|
"darkviolet": "#9400d3",
|
||||||
|
"deeppink": "#ff1493",
|
||||||
|
"deepskyblue": "#00bfff",
|
||||||
|
"dimgray": "#696969",
|
||||||
|
"dimgrey": "#696969",
|
||||||
|
"dodgerblue": "#1e90ff",
|
||||||
|
"firebrick": "#b22222",
|
||||||
|
"floralwhite": "#fffaf0",
|
||||||
|
"forestgreen": "#228b22",
|
||||||
|
"fuchsia": "#ff00ff",
|
||||||
|
"gainsboro": "#dcdcdc",
|
||||||
|
"ghostwhite": "#f8f8ff",
|
||||||
|
"goldenrod": "#daa520",
|
||||||
|
"gold": "#ffd700",
|
||||||
|
"gray": "#808080",
|
||||||
|
"green": "#008000",
|
||||||
|
"greenyellow": "#adff2f",
|
||||||
|
"grey": "#808080",
|
||||||
|
"honeydew": "#f0fff0",
|
||||||
|
"hotpink": "#ff69b4",
|
||||||
|
"indianred": "#cd5c5c",
|
||||||
|
"indigo": "#4b0082",
|
||||||
|
"ivory": "#fffff0",
|
||||||
|
"khaki": "#f0e68c",
|
||||||
|
"lavenderblush": "#fff0f5",
|
||||||
|
"lavender": "#e6e6fa",
|
||||||
|
"lawngreen": "#7cfc00",
|
||||||
|
"lemonchiffon": "#fffacd",
|
||||||
|
"lightblue": "#add8e6",
|
||||||
|
"lightcoral": "#f08080",
|
||||||
|
"lightcyan": "#e0ffff",
|
||||||
|
"lightgoldenrodyellow": "#fafad2",
|
||||||
|
"lightgray": "#d3d3d3",
|
||||||
|
"lightgreen": "#90ee90",
|
||||||
|
"lightgrey": "#d3d3d3",
|
||||||
|
"lightpink": "#ffb6c1",
|
||||||
|
"lightsalmon": "#ffa07a",
|
||||||
|
"lightseagreen": "#20b2aa",
|
||||||
|
"lightskyblue": "#87cefa",
|
||||||
|
"lightslategray": "#778899",
|
||||||
|
"lightslategrey": "#778899",
|
||||||
|
"lightsteelblue": "#b0c4de",
|
||||||
|
"lightyellow": "#ffffe0",
|
||||||
|
"lime": "#00ff00",
|
||||||
|
"limegreen": "#32cd32",
|
||||||
|
"linen": "#faf0e6",
|
||||||
|
"magenta": "#ff00ff",
|
||||||
|
"maroon": "#800000",
|
||||||
|
"mediumaquamarine": "#66cdaa",
|
||||||
|
"mediumblue": "#0000cd",
|
||||||
|
"mediumorchid": "#ba55d3",
|
||||||
|
"mediumpurple": "#9370db",
|
||||||
|
"mediumseagreen": "#3cb371",
|
||||||
|
"mediumslateblue": "#7b68ee",
|
||||||
|
"mediumspringgreen": "#00fa9a",
|
||||||
|
"mediumturquoise": "#48d1cc",
|
||||||
|
"mediumvioletred": "#c71585",
|
||||||
|
"midnightblue": "#191970",
|
||||||
|
"mintcream": "#f5fffa",
|
||||||
|
"mistyrose": "#ffe4e1",
|
||||||
|
"moccasin": "#ffe4b5",
|
||||||
|
"navajowhite": "#ffdead",
|
||||||
|
"navy": "#000080",
|
||||||
|
"oldlace": "#fdf5e6",
|
||||||
|
"olive": "#808000",
|
||||||
|
"olivedrab": "#6b8e23",
|
||||||
|
"orange": "#ffa500",
|
||||||
|
"orangered": "#ff4500",
|
||||||
|
"orchid": "#da70d6",
|
||||||
|
"palegoldenrod": "#eee8aa",
|
||||||
|
"palegreen": "#98fb98",
|
||||||
|
"paleturquoise": "#afeeee",
|
||||||
|
"palevioletred": "#db7093",
|
||||||
|
"papayawhip": "#ffefd5",
|
||||||
|
"peachpuff": "#ffdab9",
|
||||||
|
"peru": "#cd853f",
|
||||||
|
"pink": "#ffc0cb",
|
||||||
|
"plum": "#dda0dd",
|
||||||
|
"powderblue": "#b0e0e6",
|
||||||
|
"purple": "#800080",
|
||||||
|
"red": "#ff0000",
|
||||||
|
"rosybrown": "#bc8f8f",
|
||||||
|
"royalblue": "#4169e1",
|
||||||
|
"saddlebrown": "#8b4513",
|
||||||
|
"salmon": "#fa8072",
|
||||||
|
"sandybrown": "#f4a460",
|
||||||
|
"seagreen": "#2e8b57",
|
||||||
|
"seashell": "#fff5ee",
|
||||||
|
"sienna": "#a0522d",
|
||||||
|
"silver": "#c0c0c0",
|
||||||
|
"skyblue": "#87ceeb",
|
||||||
|
"slateblue": "#6a5acd",
|
||||||
|
"slategray": "#708090",
|
||||||
|
"slategrey": "#708090",
|
||||||
|
"snow": "#fffafa",
|
||||||
|
"springgreen": "#00ff7f",
|
||||||
|
"steelblue": "#4682b4",
|
||||||
|
"tan": "#d2b48c",
|
||||||
|
"teal": "#008080",
|
||||||
|
"thistle": "#d8bfd8",
|
||||||
|
"tomato": "#ff6347",
|
||||||
|
"turquoise": "#40e0d0",
|
||||||
|
"violet": "#ee82ee",
|
||||||
|
"wheat": "#f5deb3",
|
||||||
|
"white": "#ffffff",
|
||||||
|
"whitesmoke": "#f5f5f5",
|
||||||
|
"yellow": "#ffff00",
|
||||||
|
"yellowgreen": "#9acd32"
|
||||||
|
}
|
||||||
73
modules/module.getdata.js
Normal file
73
modules/module.getdata.js
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
const FormData = require('form-data');
|
||||||
|
const got = require('got');
|
||||||
|
|
||||||
|
// do req
|
||||||
|
const getData = async (options) => {
|
||||||
|
let gOptions = {
|
||||||
|
url: options.url,
|
||||||
|
headers: {
|
||||||
|
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:70.0) Gecko/20100101 Firefox/70.0',
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if(options.baseUrl){
|
||||||
|
gOptions.prefixUrl = options.baseUrl;
|
||||||
|
gOptions.url = gOptions.url.replace(/^\//,'');
|
||||||
|
}
|
||||||
|
if(options.querystring){
|
||||||
|
gOptions.url += `?${new URLSearchParams(options.querystring).toString()}`;
|
||||||
|
}
|
||||||
|
if(options.auth){
|
||||||
|
gOptions.method = 'POST';
|
||||||
|
gOptions.body = new FormData();
|
||||||
|
gOptions.body.append('username', options.auth.user);
|
||||||
|
gOptions.body.append('password', options.auth.pass);
|
||||||
|
}
|
||||||
|
if(options.useToken && options.token){
|
||||||
|
gOptions.headers.Authorization = `Token ${options.token}`;
|
||||||
|
}
|
||||||
|
if(options.dinstid){
|
||||||
|
gOptions.headers.devicetype = 'Android Phone';
|
||||||
|
}
|
||||||
|
// debug
|
||||||
|
gOptions.hooks = {
|
||||||
|
beforeRequest: [
|
||||||
|
(gotOpts) => {
|
||||||
|
if(options.debug){
|
||||||
|
console.log('[DEBUG] GOT OPTIONS:');
|
||||||
|
console.log(gotOpts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
let res = await got(gOptions);
|
||||||
|
if(res.body && res.body.match(/^</)){
|
||||||
|
throw { name: 'HTMLError', res };
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
res,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch(error){
|
||||||
|
if(options.debug){
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
if(error.response && error.response.statusCode && error.response.statusMessage){
|
||||||
|
console.log(`[ERROR] ${error.name} ${error.response.statusCode}: ${error.response.statusMessage}`);
|
||||||
|
}
|
||||||
|
else if(error.name && error.name == 'HTMLError' && error.res && error.res.body){
|
||||||
|
console.log(`[ERROR] ${error.name}:`);
|
||||||
|
console.log(error.res.body);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
console.log(`[ERROR] ${error.name}: ${error.code||error.message}`);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = getData;
|
||||||
182
modules/module.vttconvert.js
Normal file
182
modules/module.vttconvert.js
Normal file
|
|
@ -0,0 +1,182 @@
|
||||||
|
// vtt loader
|
||||||
|
function loadVtt(vttStr) {
|
||||||
|
const rx = /^([\d:.]*) --> ([\d:.]*)\s?(.*?)\s*$/;
|
||||||
|
const lines = vttStr.replace(/\r?\n/g, '\n').split('\n');
|
||||||
|
let data = [], lineBuf = [], record = null;
|
||||||
|
// check lines
|
||||||
|
for (let l of lines) {
|
||||||
|
let m = l.match(rx);
|
||||||
|
if (m) {
|
||||||
|
if (lineBuf.length > 0) {
|
||||||
|
lineBuf.pop();
|
||||||
|
}
|
||||||
|
if (record !== null) {
|
||||||
|
record.text = lineBuf.join('\n');
|
||||||
|
data.push(record);
|
||||||
|
}
|
||||||
|
record = {
|
||||||
|
time_start: m[1],
|
||||||
|
time_end: m[2],
|
||||||
|
ext_param: m[3].split(' ').map(x => x.split(':')).reduce((p, c) => (p[c[0]] = c[1]) && p, {}),
|
||||||
|
};
|
||||||
|
lineBuf = [];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
lineBuf.push(l);
|
||||||
|
}
|
||||||
|
if (record !== null) {
|
||||||
|
if (lineBuf[lineBuf.length - 1] === '') {
|
||||||
|
lineBuf.pop();
|
||||||
|
}
|
||||||
|
record.text = lineBuf.join('\n');
|
||||||
|
data.push(record);
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ass specific
|
||||||
|
function convertToAss(vttStr){
|
||||||
|
let ass = [
|
||||||
|
'\ufeff[Script Info]',
|
||||||
|
'Title: English',
|
||||||
|
'ScriptType: v4.00+',
|
||||||
|
'PlayResX: 1280',
|
||||||
|
'PlayResY: 720',
|
||||||
|
'WrapStyle: 0',
|
||||||
|
'ScaledBorderAndShadow: yes',
|
||||||
|
'',
|
||||||
|
'[V4+ Styles]',
|
||||||
|
'Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, '
|
||||||
|
+ 'Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, '
|
||||||
|
+ 'BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding',
|
||||||
|
'Style: Main,Noto Sans,55,&H00FFFFFF,&H000000FF,&H00020713,&H00000000,0,0,0,0,100,100,0,0,1,3,0,2,10,10,10,1',
|
||||||
|
'Style: MainTop,Noto Sans,55,&H00FFFFFF,&H000000FF,&H00020713,&H00000000,0,0,0,0,100,100,0,0,1,3,0,8,10,10,10,1',
|
||||||
|
'',
|
||||||
|
'[Events]',
|
||||||
|
'Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text',
|
||||||
|
];
|
||||||
|
|
||||||
|
let vttData = loadVtt(vttStr);
|
||||||
|
for (let l of vttData) {
|
||||||
|
l = convertToAssLine(l, 'Main');
|
||||||
|
ass = ass.concat(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ass.join('\r\n') + '\r\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertToAssLine(l, style) {
|
||||||
|
let start = convertTime(l.time_start);
|
||||||
|
let end = convertTime(l.time_end);
|
||||||
|
let text = convertToAssText(l.text);
|
||||||
|
|
||||||
|
// debugger
|
||||||
|
if (l.ext_param.align != 'middle') {
|
||||||
|
console.log('[WARN] Detected specific align param, please contact developer');
|
||||||
|
cosnole.log(l);
|
||||||
|
}
|
||||||
|
if (l.ext_param.vertical) {
|
||||||
|
console.log('[WARN] Detected specific vertical param, please contact developer');
|
||||||
|
cosnole.log(l);
|
||||||
|
}
|
||||||
|
if (l.ext_param.line && l.ext_param.line != '7%') {
|
||||||
|
console.log('[WARN] Detected specific line param, please contact developer');
|
||||||
|
cosnole.log(l);
|
||||||
|
}
|
||||||
|
if (l.ext_param.position) {
|
||||||
|
console.log('[WARN] Detected specific position param, please contact developer');
|
||||||
|
cosnole.log(l);
|
||||||
|
}
|
||||||
|
if (l.text.match(/<font/)){
|
||||||
|
console.log('[WARN] Detected specific color param, please contact developer');
|
||||||
|
cosnole.log(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (l.ext_param.line === '7%') {
|
||||||
|
style = 'MainTop';
|
||||||
|
}
|
||||||
|
|
||||||
|
return `Dialogue: 0,${start},${end},${style},,0,0,0,,${text}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertToAssText(text) {
|
||||||
|
text = text
|
||||||
|
.replace(/\r/g, '')
|
||||||
|
.replace(/\n/g, '\\N')
|
||||||
|
.replace(/\\N +/g, '\\N')
|
||||||
|
.replace(/ +\\N/g, '\\N')
|
||||||
|
.replace(/(\\N)+/g, '\\N')
|
||||||
|
.replace(/<b[^>]*>([^<]*)<\/b>/g, '{\\b1}$1{\\b0}')
|
||||||
|
.replace(/<i[^>]*>([^<]*)<\/i>/g, '{\\i1}$1{\\i0}')
|
||||||
|
.replace(/<u[^>]*>([^<]*)<\/u>/g, '{\\u1}$1{\\u0}')
|
||||||
|
// .replace(/<c[^>]*>[^<]*<\/c>/g, '')
|
||||||
|
// .replace(/<ruby[^>]*>[^<]*<\/ruby>/g, '')
|
||||||
|
.replace(/<[^>]>/g, '')
|
||||||
|
.replace(/\\N$/, '')
|
||||||
|
.replace(/ +$/, '');
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
// srt specific
|
||||||
|
function convertToSrt(vttStr){
|
||||||
|
let srt = [], srtLineIdx = 0;
|
||||||
|
|
||||||
|
let vttData = loadVtt(vttStr);
|
||||||
|
for (let l of vttData) {
|
||||||
|
srtLineIdx++;
|
||||||
|
l = convertToSrtLine(l, srtLineIdx);
|
||||||
|
srt = srt.concat(l);
|
||||||
|
}
|
||||||
|
|
||||||
|
return srt.join('\r\n') + '\r\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertToSrtLine(l, idx) {
|
||||||
|
let bom = idx == 1 ? '\ufeff' : '';
|
||||||
|
let start = convertTime(l.time_start, true);
|
||||||
|
let end = convertTime(l.time_end, true);
|
||||||
|
let text = l.text;
|
||||||
|
return `${bom}${idx}\r\n${start} --> ${end}\r\n${text}\r\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// time parser
|
||||||
|
function convertTime(time, srtFormat) {
|
||||||
|
let mTime = time.match(/([\d:]*)\.?(\d*)/);
|
||||||
|
if (!mTime){
|
||||||
|
return srtFormat ? '00:00:00,000' : '0:00:00.00';
|
||||||
|
}
|
||||||
|
return toSubsTime(mTime[0], srtFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toSubsTime(str, srtFormat) {
|
||||||
|
|
||||||
|
let n = [], x, sx;
|
||||||
|
x = str.split(/[:.]/).map(x => Number(x));
|
||||||
|
|
||||||
|
let msLen = srtFormat ? 3 : 2;
|
||||||
|
let hLen = srtFormat ? 2 : 1;
|
||||||
|
|
||||||
|
x[3] = '0.' + ('' + x[3]).padStart(3, '0');
|
||||||
|
sx = x[0]*60*60 + x[1]*60 + x[2] + Number(x[3])
|
||||||
|
sx = sx.toFixed(msLen).split('.');
|
||||||
|
|
||||||
|
|
||||||
|
n.unshift(padTimeNum('.', sx[1], msLen));
|
||||||
|
sx = Number(sx[0]);
|
||||||
|
|
||||||
|
n.unshift(padTimeNum(':', sx%60, 2));
|
||||||
|
n.unshift(padTimeNum(':', Math.floor(sx/60)%60, 2));
|
||||||
|
n.unshift(padTimeNum('', Math.floor(sx/3600)%60, hLen));
|
||||||
|
|
||||||
|
return n.join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
function padTimeNum(sep, input, pad){
|
||||||
|
return sep + ('' + input).padStart(pad, '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
// export module
|
||||||
|
module.exports = (vttStr, toSrt) => {
|
||||||
|
const convert = toSrt ? convertToSrt : convertToAss;
|
||||||
|
return convert(vttStr);
|
||||||
|
};
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "funimation-downloader-nx",
|
"name": "funimation-downloader-nx",
|
||||||
"short_name": "funi",
|
"short_name": "funi",
|
||||||
"version": "4.6.1",
|
"version": "4.7.0-beta.1",
|
||||||
"description": "Download videos from Funimation via cli.",
|
"description": "Download videos from Funimation via cli.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"download",
|
"download",
|
||||||
|
|
@ -29,11 +29,9 @@
|
||||||
"hls-download": "^2.5.3",
|
"hls-download": "^2.5.3",
|
||||||
"lookpath": "^1.1.0",
|
"lookpath": "^1.1.0",
|
||||||
"m3u8-parsed": "^1.3.0",
|
"m3u8-parsed": "^1.3.0",
|
||||||
"proxy-agent": "^3.1.1",
|
|
||||||
"sei-helper": "^3.3.0",
|
"sei-helper": "^3.3.0",
|
||||||
"ttml2srt": "^1.2.0",
|
|
||||||
"yaml": "^1.10.0",
|
"yaml": "^1.10.0",
|
||||||
"yargs": "^16.0.3"
|
"yargs": "^16.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"fs-extra": "^9.0.1",
|
"fs-extra": "^9.0.1",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue