mirror of
https://github.com/anidl/multi-downloader-nx.git
synced 2026-03-11 17:45:30 +00:00
FFmpeg with multiple language files
This commit is contained in:
parent
6edf34a768
commit
5a95739e33
7 changed files with 307 additions and 280 deletions
|
|
@ -1,2 +1 @@
|
||||||
content: ./videos/
|
content: ./videos/
|
||||||
trash: ./videos/_trash/
|
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ After installing NodeJS with NPM go to directory with `package.json` file and ty
|
||||||
|
|
||||||
### Utility
|
### Utility
|
||||||
|
|
||||||
* `--nocleanup` move unnecessary files to trash folder after completion instead of deleting
|
* `--nocleanup` don't delete the input files after the muxing
|
||||||
* `-h`, `--help` show all options
|
* `-h`, `--help` show all options
|
||||||
|
|
||||||
## Filename Template
|
## Filename Template
|
||||||
|
|
|
||||||
501
funi.js
501
funi.js
|
|
@ -84,8 +84,8 @@ let title = '',
|
||||||
fnEpNum = 0,
|
fnEpNum = 0,
|
||||||
fnOutput = '',
|
fnOutput = '',
|
||||||
season = 0,
|
season = 0,
|
||||||
tsDlPath = false,
|
tsDlPath = [],
|
||||||
stDlPath = undefined;
|
stDlPath = [];
|
||||||
|
|
||||||
// select mode
|
// select mode
|
||||||
if(argv.auth){
|
if(argv.auth){
|
||||||
|
|
@ -284,7 +284,7 @@ async function getEpisode(fnSlug){
|
||||||
debug: argv.debug,
|
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], streamIds = [];
|
||||||
// build fn
|
// build fn
|
||||||
showTitle = ep.parent.title;
|
showTitle = ep.parent.title;
|
||||||
title = ep.title;
|
title = ep.title;
|
||||||
|
|
@ -334,7 +334,8 @@ async function getEpisode(fnSlug){
|
||||||
'enUS': 'English',
|
'enUS': 'English',
|
||||||
'esLA': 'Spanish (Latin Am)',
|
'esLA': 'Spanish (Latin Am)',
|
||||||
'ptBR': 'Portuguese (Brazil)',
|
'ptBR': 'Portuguese (Brazil)',
|
||||||
'zhMN': 'Chinese (Mandarin, PRC)'
|
'zhMN': 'Chinese (Mandarin, PRC)',
|
||||||
|
'jpJP': 'Japanese'
|
||||||
};
|
};
|
||||||
|
|
||||||
// select
|
// select
|
||||||
|
|
@ -343,55 +344,72 @@ async function getEpisode(fnSlug){
|
||||||
let selected = false;
|
let selected = false;
|
||||||
if(m.id > 0 && m.type == 'Non-Encrypted'){
|
if(m.id > 0 && m.type == 'Non-Encrypted'){
|
||||||
let dub_type = m.language;
|
let dub_type = m.language;
|
||||||
|
let localSubs = []
|
||||||
let selUncut = !argv.simul && uncut[dub_type] && m.version.match(/uncut/i)
|
let selUncut = !argv.simul && uncut[dub_type] && m.version.match(/uncut/i)
|
||||||
? true
|
? true
|
||||||
: (!uncut[dub_type] || argv.simul && m.version.match(/simulcast/i) ? true : false);
|
: (!uncut[dub_type] || argv.simul && m.version.match(/simulcast/i) ? true : false);
|
||||||
if(dub_type == 'Japanese' && argv.sub && selUncut){
|
for (let curDub of argv.dub) {
|
||||||
streamId = m.id;
|
if(dub_type == dubType[curDub] && selUncut){
|
||||||
stDlPath = m.subtitles;
|
streamIds.push({
|
||||||
selected = true;
|
id: m.id,
|
||||||
}
|
lang: merger.getLanguageCode(dubType[curDub], curDub.slice(0, -2))
|
||||||
else if(dub_type == dubType[argv.dub] && !argv.sub && selUncut){
|
});
|
||||||
streamId = m.id;
|
stDlPath.push(...m.subtitles);
|
||||||
stDlPath = m.subtitles;
|
localSubs = m.subtitles
|
||||||
selected = true;
|
selected = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
console.log(`[#${m.id}] ${dub_type} [${m.version}]${(selected?' (selected)':'')}${
|
console.log(`[#${m.id}] ${dub_type} [${m.version}]${(selected?' (selected)':'')}${
|
||||||
stDlPath && selected ? ` (using ${stDlPath.map(a => `'${a.langName}'`).join(', ')} for subtitles)` : ''
|
localSubs && localSubs.length > 0 && selected ? ` (using ${localSubs.map(a => `'${a.langName}'`).join(', ')} for subtitles)` : ''
|
||||||
}`);
|
}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let already = []
|
||||||
|
stDlPath = stDlPath.filter(a => {
|
||||||
|
if (already.includes(a.language)) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
already.push(a.language)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
if(streamId<1){
|
if(streamIds.length <1){
|
||||||
console.log('[ERROR] Track not selected\n');
|
console.log('[ERROR] Track not selected\n');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
let streamData = await getData({
|
tsDlPath = [];
|
||||||
baseUrl: api_host,
|
for (let streamId of streamIds) {
|
||||||
url: `/source/catalog/video/${streamId}/signed`,
|
let streamData = await getData({
|
||||||
token: token,
|
baseUrl: api_host,
|
||||||
dinstid: 'uuid',
|
url: `/source/catalog/video/${streamId.id}/signed`,
|
||||||
useToken: true,
|
token: token,
|
||||||
useProxy: true,
|
dinstid: 'uuid',
|
||||||
debug: argv.debug,
|
useToken: true,
|
||||||
});
|
useProxy: true,
|
||||||
if(!streamData.ok){return;}
|
debug: argv.debug,
|
||||||
streamData = JSON.parse(streamData.res.body);
|
});
|
||||||
tsDlPath = false;
|
if(!streamData.ok){return;}
|
||||||
if(streamData.errors){
|
streamData = JSON.parse(streamData.res.body);
|
||||||
console.log('[ERROR] Error #%s: %s\n',streamData.errors[0].code,streamData.errors[0].detail);
|
if(streamData.errors){
|
||||||
return;
|
console.log('[ERROR] Error #%s: %s\n',streamData.errors[0].code,streamData.errors[0].detail);
|
||||||
}
|
return;
|
||||||
else{
|
}
|
||||||
for(let u in streamData.items){
|
else{
|
||||||
if(streamData.items[u].videoType == 'm3u8'){
|
for(let u in streamData.items){
|
||||||
tsDlPath = streamData.items[u].src;
|
if(streamData.items[u].videoType == 'm3u8'){
|
||||||
break;
|
tsDlPath.push({
|
||||||
|
path: streamData.items[u].src,
|
||||||
|
lang: streamId.lang
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(!tsDlPath){
|
if(tsDlPath.length < 1){
|
||||||
console.log('[ERROR] Unknown error\n');
|
console.log('[ERROR] Unknown error\n');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -443,178 +461,213 @@ function getSubsUrl(m){
|
||||||
async function downloadStreams(){
|
async function downloadStreams(){
|
||||||
|
|
||||||
// req playlist
|
// req playlist
|
||||||
let plQualityReq = await getData({
|
|
||||||
url: tsDlPath,
|
|
||||||
useProxy: (argv.ssp ? false : true),
|
|
||||||
debug: argv.debug,
|
|
||||||
});
|
|
||||||
if(!plQualityReq.ok){return;}
|
|
||||||
|
|
||||||
let plQualityLinkList = m3u8(plQualityReq.res.body);
|
|
||||||
|
|
||||||
let mainServersList = [
|
|
||||||
'vmfst-api.prd.funimationsvc.com',
|
|
||||||
'd132fumi6di1wa.cloudfront.net',
|
|
||||||
'funiprod.akamaized.net',
|
|
||||||
];
|
|
||||||
|
|
||||||
let plServerList = [],
|
|
||||||
plStreams = {},
|
|
||||||
plLayersStr = [],
|
|
||||||
plLayersRes = {},
|
|
||||||
plMaxLayer = 1,
|
|
||||||
plNewIds = 1,
|
|
||||||
plAud = { uri: '' };
|
|
||||||
|
|
||||||
// new uris
|
|
||||||
let vplReg = /streaming_video_(\d+)_(\d+)_(\d+)_index\.m3u8/;
|
|
||||||
if(plQualityLinkList.playlists[0].uri.match(vplReg)){
|
|
||||||
if(plQualityLinkList.mediaGroups.AUDIO['audio-aacl-128']){
|
|
||||||
let audioData = plQualityLinkList.mediaGroups.AUDIO['audio-aacl-128'],
|
|
||||||
audioEl = Object.keys(audioData);
|
|
||||||
audioData = audioData[audioEl[0]];
|
|
||||||
plAud = { ...audioData, ...{ langStr: audioEl[0] } };
|
|
||||||
}
|
|
||||||
plQualityLinkList.playlists.sort((a, b) => {
|
|
||||||
let av = parseInt(a.uri.match(vplReg)[3]);
|
|
||||||
let bv = parseInt(b.uri.match(vplReg)[3]);
|
|
||||||
if(av > bv){
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (av < bv) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
for(let s of plQualityLinkList.playlists){
|
|
||||||
if(s.uri.match(/_Layer(\d+)\.m3u8/) || s.uri.match(vplReg)){
|
|
||||||
// set layer and max layer
|
|
||||||
let plLayerId = 0;
|
|
||||||
if(s.uri.match(/_Layer(\d+)\.m3u8/)){
|
|
||||||
plLayerId = parseInt(s.uri.match(/_Layer(\d+)\.m3u8/)[1]);
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
plLayerId = plNewIds, plNewIds++;
|
|
||||||
}
|
|
||||||
plMaxLayer = plMaxLayer < plLayerId ? plLayerId : plMaxLayer;
|
|
||||||
// set urls and servers
|
|
||||||
let plUrlDl = s.uri;
|
|
||||||
let plServer = new URL(plUrlDl).host;
|
|
||||||
if(!plServerList.includes(plServer)){
|
|
||||||
plServerList.push(plServer);
|
|
||||||
}
|
|
||||||
if(!Object.keys(plStreams).includes(plServer)){
|
|
||||||
plStreams[plServer] = {};
|
|
||||||
}
|
|
||||||
if(plStreams[plServer][plLayerId] && plStreams[plServer][plLayerId] != plUrlDl){
|
|
||||||
console.log(`[WARN] Non duplicate url for ${plServer} detected, please report to developer!`);
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
plStreams[plServer][plLayerId] = plUrlDl;
|
|
||||||
}
|
|
||||||
// set plLayersStr
|
|
||||||
let plResolution = s.attributes.RESOLUTION;
|
|
||||||
plLayersRes[plLayerId] = plResolution;
|
|
||||||
let plBandwidth = Math.round(s.attributes.BANDWIDTH/1024);
|
|
||||||
if(plLayerId<10){
|
|
||||||
plLayerId = plLayerId.toString().padStart(2,' ');
|
|
||||||
}
|
|
||||||
let qualityStrAdd = `${plLayerId}: ${plResolution.width}x${plResolution.height} (${plBandwidth}KiB/s)`;
|
|
||||||
let qualityStrRegx = new RegExp(qualityStrAdd.replace(/(:|\(|\)|\/)/g,'\\$1'),'m');
|
|
||||||
let qualityStrMatch = !plLayersStr.join('\r\n').match(qualityStrRegx);
|
|
||||||
if(qualityStrMatch){
|
|
||||||
plLayersStr.push(qualityStrAdd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.log(s.uri);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for(let s of mainServersList){
|
|
||||||
if(plServerList.includes(s)){
|
|
||||||
plServerList.splice(plServerList.indexOf(s), 1);
|
|
||||||
plServerList.unshift(s);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(typeof argv.q == 'object' && argv.q.length > 1){
|
|
||||||
argv.q = argv.q[argv.q.length-1];
|
|
||||||
}
|
|
||||||
|
|
||||||
argv.q = argv.q < 1 || argv.q > plMaxLayer ? plMaxLayer : argv.q;
|
|
||||||
|
|
||||||
let plSelectedServer = plServerList[argv.x-1];
|
|
||||||
let plSelectedList = plStreams[plSelectedServer];
|
|
||||||
let videoUrl = argv.x < plServerList.length+1 && plSelectedList[argv.q] ? plSelectedList[argv.q] : '';
|
|
||||||
|
|
||||||
plLayersStr.sort();
|
|
||||||
console.log(`[INFO] Servers available:\n\t${plServerList.join('\n\t')}`);
|
|
||||||
console.log(`[INFO] Available qualities:\n\t${plLayersStr.join('\n\t')}`);
|
|
||||||
|
|
||||||
if(videoUrl != ''){
|
|
||||||
console.log(`[INFO] Selected layer: ${argv.q} (${plLayersRes[argv.q].width}x${plLayersRes[argv.q].height}) @ ${plSelectedServer}`);
|
|
||||||
console.log('[INFO] Stream URL:',videoUrl);
|
|
||||||
|
|
||||||
fnOutput = parseFileName(argv.fileName, title, fnEpNum, showTitle, season, plLayersRes[argv.q].width, plLayersRes[argv.q].height);
|
let purvideo = []
|
||||||
console.log(`[INFO] Output filename: ${fnOutput}.ts`);
|
let puraudio = []
|
||||||
}
|
let audioAndVideo = []
|
||||||
else if(argv.x > plServerList.length){
|
let outName;
|
||||||
console.log('[ERROR] Server not selected!\n');
|
for (let streamPath of tsDlPath) {
|
||||||
return;
|
let plQualityReq = await getData({
|
||||||
}
|
url: streamPath.path,
|
||||||
else{
|
|
||||||
console.log('[ERROR] Layer not selected!\n');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let dlFailed = false;
|
|
||||||
let dlFailedA = false;
|
|
||||||
|
|
||||||
|
|
||||||
video: if (!argv.novids) {
|
|
||||||
// download video
|
|
||||||
let reqVideo = await getData({
|
|
||||||
url: videoUrl,
|
|
||||||
useProxy: (argv.ssp ? false : true),
|
useProxy: (argv.ssp ? false : true),
|
||||||
debug: argv.debug,
|
debug: argv.debug,
|
||||||
});
|
});
|
||||||
if (!reqVideo.ok) { break video; }
|
if(!plQualityReq.ok){return;}
|
||||||
|
|
||||||
let chunkList = m3u8(reqVideo.res.body);
|
let plQualityLinkList = m3u8(plQualityReq.res.body);
|
||||||
|
|
||||||
let tsFile = path.join(cfg.dir.content, fnOutput);
|
let mainServersList = [
|
||||||
dlFailed = !await downloadFile(tsFile, chunkList);
|
'vmfst-api.prd.funimationsvc.com',
|
||||||
}
|
'd132fumi6di1wa.cloudfront.net',
|
||||||
else{
|
'funiprod.akamaized.net',
|
||||||
console.log('[INFO] Skip video downloading...\n');
|
];
|
||||||
|
|
||||||
|
let plServerList = [],
|
||||||
|
plStreams = {},
|
||||||
|
plLayersStr = [],
|
||||||
|
plLayersRes = {},
|
||||||
|
plMaxLayer = 1,
|
||||||
|
plNewIds = 1,
|
||||||
|
plAud = { uri: '' };
|
||||||
|
|
||||||
|
// new uris
|
||||||
|
let vplReg = /streaming_video_(\d+)_(\d+)_(\d+)_index\.m3u8/;
|
||||||
|
if(plQualityLinkList.playlists[0].uri.match(vplReg)){
|
||||||
|
if(plQualityLinkList.mediaGroups.AUDIO['audio-aacl-128']){
|
||||||
|
let audioData = plQualityLinkList.mediaGroups.AUDIO['audio-aacl-128'],
|
||||||
|
audioEl = Object.keys(audioData);
|
||||||
|
audioData = audioData[audioEl[0]];
|
||||||
|
plAud = { ...audioData, ...{ langStr: audioEl[0] } };
|
||||||
|
}
|
||||||
|
plQualityLinkList.playlists.sort((a, b) => {
|
||||||
|
let av = parseInt(a.uri.match(vplReg)[3]);
|
||||||
|
let bv = parseInt(b.uri.match(vplReg)[3]);
|
||||||
|
if(av > bv){
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (av < bv) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for(let s of plQualityLinkList.playlists){
|
||||||
|
if(s.uri.match(/_Layer(\d+)\.m3u8/) || s.uri.match(vplReg)){
|
||||||
|
// set layer and max layer
|
||||||
|
let plLayerId = 0;
|
||||||
|
if(s.uri.match(/_Layer(\d+)\.m3u8/)){
|
||||||
|
plLayerId = parseInt(s.uri.match(/_Layer(\d+)\.m3u8/)[1]);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
plLayerId = plNewIds, plNewIds++;
|
||||||
|
}
|
||||||
|
plMaxLayer = plMaxLayer < plLayerId ? plLayerId : plMaxLayer;
|
||||||
|
// set urls and servers
|
||||||
|
let plUrlDl = s.uri;
|
||||||
|
let plServer = new URL(plUrlDl).host;
|
||||||
|
if(!plServerList.includes(plServer)){
|
||||||
|
plServerList.push(plServer);
|
||||||
|
}
|
||||||
|
if(!Object.keys(plStreams).includes(plServer)){
|
||||||
|
plStreams[plServer] = {};
|
||||||
|
}
|
||||||
|
if(plStreams[plServer][plLayerId] && plStreams[plServer][plLayerId] != plUrlDl){
|
||||||
|
console.log(`[WARN] Non duplicate url for ${plServer} detected, please report to developer!`);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
plStreams[plServer][plLayerId] = plUrlDl;
|
||||||
|
}
|
||||||
|
// set plLayersStr
|
||||||
|
let plResolution = s.attributes.RESOLUTION;
|
||||||
|
plLayersRes[plLayerId] = plResolution;
|
||||||
|
let plBandwidth = Math.round(s.attributes.BANDWIDTH/1024);
|
||||||
|
if(plLayerId<10){
|
||||||
|
plLayerId = plLayerId.toString().padStart(2,' ');
|
||||||
|
}
|
||||||
|
let qualityStrAdd = `${plLayerId}: ${plResolution.width}x${plResolution.height} (${plBandwidth}KiB/s)`;
|
||||||
|
let qualityStrRegx = new RegExp(qualityStrAdd.replace(/(:|\(|\)|\/)/g,'\\$1'),'m');
|
||||||
|
let qualityStrMatch = !plLayersStr.join('\r\n').match(qualityStrRegx);
|
||||||
|
if(qualityStrMatch){
|
||||||
|
plLayersStr.push(qualityStrAdd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log(s.uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(let s of mainServersList){
|
||||||
|
if(plServerList.includes(s)){
|
||||||
|
plServerList.splice(plServerList.indexOf(s), 1);
|
||||||
|
plServerList.unshift(s);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(typeof argv.q == 'object' && argv.q.length > 1){
|
||||||
|
argv.q = argv.q[argv.q.length-1];
|
||||||
|
}
|
||||||
|
|
||||||
|
argv.q = argv.q < 1 || argv.q > plMaxLayer ? plMaxLayer : argv.q;
|
||||||
|
|
||||||
|
let plSelectedServer = plServerList[argv.x-1];
|
||||||
|
let plSelectedList = plStreams[plSelectedServer];
|
||||||
|
let videoUrl = argv.x < plServerList.length+1 && plSelectedList[argv.q] ? plSelectedList[argv.q] : '';
|
||||||
|
|
||||||
|
plLayersStr.sort();
|
||||||
|
console.log(`[INFO] Servers available:\n\t${plServerList.join('\n\t')}`);
|
||||||
|
console.log(`[INFO] Available qualities:\n\t${plLayersStr.join('\n\t')}`);
|
||||||
|
|
||||||
|
if(videoUrl != ''){
|
||||||
|
console.log(`[INFO] Selected layer: ${argv.q} (${plLayersRes[argv.q].width}x${plLayersRes[argv.q].height}) @ ${plSelectedServer}`);
|
||||||
|
console.log('[INFO] Stream URL:',videoUrl);
|
||||||
|
|
||||||
|
fnOutput = parseFileName(argv.fileName, title, fnEpNum, showTitle, season, plLayersRes[argv.q].width, plLayersRes[argv.q].height);
|
||||||
|
outName = fnOutput;
|
||||||
|
console.log(`[INFO] Output filename: ${fnOutput}.ts`);
|
||||||
|
}
|
||||||
|
else if(argv.x > plServerList.length){
|
||||||
|
console.log('[ERROR] Server not selected!\n');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
console.log('[ERROR] Layer not selected!\n');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dlFailed = false;
|
||||||
|
let dlFailedA = false;
|
||||||
|
|
||||||
|
video: if (!argv.novids) {
|
||||||
|
if (plAud.uri && (purvideo.length > 1 || audioAndVideo.length > 1)) {
|
||||||
|
console.log("break 1")
|
||||||
|
break video;
|
||||||
|
} else if (!plAud.uri && (audioAndVideo.some(a => a.lang === streamPath.lang) || puraudio.some(a => a.lang === streamPath.lang))) {
|
||||||
|
console.log("break 2")
|
||||||
|
break video;
|
||||||
|
}
|
||||||
|
// download video
|
||||||
|
let reqVideo = await getData({
|
||||||
|
url: videoUrl,
|
||||||
|
useProxy: (argv.ssp ? false : true),
|
||||||
|
debug: argv.debug,
|
||||||
|
});
|
||||||
|
if (!reqVideo.ok) { break video; }
|
||||||
|
|
||||||
|
let chunkList = m3u8(reqVideo.res.body);
|
||||||
|
|
||||||
|
let tsFile = path.join(cfg.dir.content, `${fnOutput}.video${(plAud.uri ? '' : '.' + streamPath.lang )}`);
|
||||||
|
dlFailed = !await downloadFile(tsFile, chunkList);
|
||||||
|
if (!dlFailed) {
|
||||||
|
if (plAud.uri) {
|
||||||
|
purvideo.push({
|
||||||
|
path: `${tsFile}.ts`,
|
||||||
|
lang: plAud.language
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
audioAndVideo.push({
|
||||||
|
path: `${tsFile}.ts`,
|
||||||
|
lang: streamPath.lang
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
console.log('[INFO] Skip video downloading...\n');
|
||||||
|
}
|
||||||
|
if (!argv.noaudio && plAud.uri) {
|
||||||
|
// download audio
|
||||||
|
if (audioAndVideo.some(a => a.lang === plAud.language) || puraudio.some(a => a.lang === plAud.language))
|
||||||
|
return;
|
||||||
|
let reqAudio = await getData({
|
||||||
|
url: plAud.uri,
|
||||||
|
useProxy: (argv.ssp ? false : true),
|
||||||
|
debug: argv.debug,
|
||||||
|
});
|
||||||
|
if (!reqAudio.ok) { return; }
|
||||||
|
|
||||||
|
let chunkListA = m3u8(reqAudio.res.body);
|
||||||
|
|
||||||
|
let tsFileA = path.join(cfg.dir.content, fnOutput + `.audio.${plAud.language}`);
|
||||||
|
|
||||||
|
dlFailedA = !await downloadFile(tsFileA, chunkListA);
|
||||||
|
if (!dlFailedA)
|
||||||
|
puraudio.push({
|
||||||
|
path: `${tsFileA}.ts`,
|
||||||
|
lang: plAud.language
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!argv.noaudio && plAud.uri) {
|
|
||||||
// download audio
|
|
||||||
let reqAudio = await getData({
|
|
||||||
url: plAud.uri,
|
|
||||||
useProxy: (argv.ssp ? false : true),
|
|
||||||
debug: argv.debug,
|
|
||||||
});
|
|
||||||
if (!reqAudio.ok) { return; }
|
|
||||||
|
|
||||||
let chunkListA = m3u8(reqAudio.res.body);
|
|
||||||
|
|
||||||
let tsFileA = path.join(cfg.dir.content, fnOutput + `.${plAud.language}`);
|
|
||||||
|
|
||||||
dlFailedA = !await downloadFile(tsFileA, chunkListA);
|
|
||||||
}
|
|
||||||
|
|
||||||
// add subs
|
// add subs
|
||||||
let subsExt = !argv.mp4 || argv.mp4 && !argv.mks && argv.ass ? '.ass' : '.srt';
|
let subsExt = !argv.mp4 || argv.mp4 && !argv.mks && argv.ass ? '.ass' : '.srt';
|
||||||
let addSubs = argv.mks && tsDlPath ? true : false;
|
let addSubs = argv.mks && tsDlPath ? true : false;
|
||||||
|
|
||||||
// download subtitles
|
// download subtitles
|
||||||
if(stDlPath){
|
if(stDlPath.length > 0){
|
||||||
console.log('[INFO] Downloading subtitles...');
|
console.log('[INFO] Downloading subtitles...');
|
||||||
for (let subObject of stDlPath) {
|
for (let subObject of stDlPath) {
|
||||||
let subsSrc = await getData({
|
let subsSrc = await getData({
|
||||||
|
|
@ -625,7 +678,6 @@ async function downloadStreams(){
|
||||||
if(subsSrc.ok){
|
if(subsSrc.ok){
|
||||||
let assData = vttConvert(subsSrc.res.body, (subsExt == '.srt' ? true : false), subObject.langName, argv.fontSize);
|
let assData = vttConvert(subsSrc.res.body, (subsExt == '.srt' ? true : false), subObject.langName, argv.fontSize);
|
||||||
subObject.file = path.join(cfg.dir.content, fnOutput) + subObject.ext + subsExt;
|
subObject.file = path.join(cfg.dir.content, fnOutput) + subObject.ext + subsExt;
|
||||||
subObject.trashFile = path.join(cfg.dir.trash, fnOutput) + subObject.ext + subsExt;
|
|
||||||
fs.writeFileSync(subObject.file, assData);
|
fs.writeFileSync(subObject.file, assData);
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
|
|
@ -638,37 +690,19 @@ async function downloadStreams(){
|
||||||
console.log('[INFO] Subtitles downloaded!');
|
console.log('[INFO] Subtitles downloaded!');
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO Remove */
|
if((puraudio.length < 1 && audioAndVideo.length < 1) || (purvideo.length < 1 && audioAndVideo.length < 1)){
|
||||||
if(false && (dlFailed || dlFailedA)){
|
console.log('\n[INFO] Unable to locate a video AND audio file\n');
|
||||||
console.log('\n[INFO] TS file not fully downloaded, skip muxing video...\n');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(argv.skipmux){
|
if(argv.skipmux){
|
||||||
|
console.log("[INFO] Skipping muxing...")
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let muxTrg = path.join(cfg.dir.content, fnOutput);
|
|
||||||
let muxTrgA = '';
|
|
||||||
let tshTrg = path.join(cfg.dir.trash, fnOutput);
|
|
||||||
let tshTrgA = '';
|
|
||||||
|
|
||||||
if(!fs.existsSync(`${muxTrg}.ts`) || !fs.statSync(`${muxTrg}.ts`).isFile()){
|
|
||||||
console.log('\n[INFO] TS file not found, skip muxing video...\n');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(plAud.uri){
|
|
||||||
muxTrgA = path.join(cfg.dir.content, fnOutput + `.${plAud.language}`);
|
|
||||||
tshTrgA = path.join(cfg.dir.trash, fnOutput + `.${plAud.language}`);
|
|
||||||
if(!fs.existsSync(`${muxTrgA}.ts`) || !fs.statSync(`${muxTrgA}.ts`).isFile()){
|
|
||||||
console.log('\n[INFO] TS file not found, skip muxing video...\n');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// usage
|
// usage
|
||||||
let usableMKVmerge = true;
|
/* TODO MkvMerge */
|
||||||
|
let usableMKVmerge = false;
|
||||||
let usableFFmpeg = true;
|
let usableFFmpeg = true;
|
||||||
|
|
||||||
// check exec path
|
// check exec path
|
||||||
|
|
@ -690,44 +724,23 @@ async function downloadStreams(){
|
||||||
|
|
||||||
if(!argv.mp4 && usableMKVmerge){
|
if(!argv.mp4 && usableMKVmerge){
|
||||||
let ffext = !argv.mp4 ? 'mkv' : 'mp4';
|
let ffext = !argv.mp4 ? 'mkv' : 'mp4';
|
||||||
let command = merger.buildCommandMkvMerge(`${muxTrg}.ts`, plAud, stDlPath, `${muxTrg}.${ffext}`);
|
let command = merger.buildCommandMkvMerge(audioAndVideo, purvideo, puraudio, stDlPath, `${path.join(cfg.dir.content, outName)}.${ffext}`);
|
||||||
console.log(command);
|
console.log(command);
|
||||||
shlp.exec('mkvmerge', `"${mkvmergebinfile}"`, command);
|
shlp.exec('mkvmerge', `"${mkvmergebinfile}"`, command);
|
||||||
}
|
}
|
||||||
else if(usableFFmpeg){
|
else if(usableFFmpeg){
|
||||||
let ffext = !argv.mp4 ? 'mkv' : 'mp4';
|
let ffext = !argv.mp4 ? 'mkv' : 'mp4';
|
||||||
let command = merger.buildCommandFFmpeg(`${muxTrg}.ts`, plAud, stDlPath, `${muxTrg}.${ffext}`);
|
let command = merger.buildCommandFFmpeg(audioAndVideo, purvideo, puraudio, stDlPath, `${path.join(cfg.dir.content, outName)}.${ffext}`);
|
||||||
shlp.exec('ffmpeg',`"${ffmpegbinfile}"`,command);
|
shlp.exec('ffmpeg',`"${ffmpegbinfile}"`,command);
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
console.log('\n[INFO] Done!\n');
|
console.log('\n[INFO] Done!\n');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
/* TODO Remove */
|
|
||||||
if (argv.nocleanup)
|
if (argv.nocleanup)
|
||||||
return;
|
return;
|
||||||
if(argv.notrashfolder && argv.nocleanup){
|
|
||||||
// don't move or delete temp files
|
audioAndVideo.concat(puraudio).concat(purvideo).forEach(a => fs.unlinkSync(a.path))
|
||||||
}
|
|
||||||
else if(argv.nocleanup){
|
|
||||||
fs.renameSync(muxTrg+'.ts', tshTrg + '.ts');
|
|
||||||
if (plAud.uri)
|
|
||||||
fs.renameSync(muxTrgA+'.ts', tshTrgA + '.ts');
|
|
||||||
if(addSubs){
|
|
||||||
for (let subObj of stDlPath) {
|
|
||||||
fs.renameSync(subObj.file ? subObj.file : muxTrg+subsExt, subObj.trashFile ? subObj.trashFile : tshTrg + subsExt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
fs.unlinkSync(muxTrg+'.ts');
|
|
||||||
if (plAud.uri)
|
|
||||||
fs.unlinkSync(muxTrgA+'.ts');
|
|
||||||
if(addSubs){
|
|
||||||
for (let subObj of stDlPath)
|
|
||||||
fs.unlinkSync(subObj.file ? subObj.file : muxTrg + subsExt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.log('\n[INFO] Done!\n');
|
console.log('\n[INFO] Done!\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,6 @@ const nodeVer = 'node14-';
|
||||||
fs.mkdirSync(`${buildDir}/bin`);
|
fs.mkdirSync(`${buildDir}/bin`);
|
||||||
fs.mkdirSync(`${buildDir}/config`);
|
fs.mkdirSync(`${buildDir}/config`);
|
||||||
fs.mkdirSync(`${buildDir}/videos`);
|
fs.mkdirSync(`${buildDir}/videos`);
|
||||||
fs.mkdirSync(`${buildDir}/videos/_trash`);
|
|
||||||
fs.copySync('./bin/', `${buildDir}/bin/`);
|
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`);
|
||||||
|
|
|
||||||
|
|
@ -2,30 +2,59 @@ const iso639 = require('iso-639');
|
||||||
const argv = require('../funi').argv;
|
const argv = require('../funi').argv;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} videoFile
|
* @param {Array<object>} videoAndAudio
|
||||||
* @param {object} audioFile
|
* @param {Array<object>} onlyVid
|
||||||
|
* @param {Array<object>} onlyAuido
|
||||||
* @param {Array<object>} subtitles
|
* @param {Array<object>} subtitles
|
||||||
|
* @param {string} output
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
const buildCommandFFmpeg = (videoFile, audioSettings, subtitles, output) => {
|
const buildCommandFFmpeg = (videoAndAudio, onlyVid, onlyAuido, subtitles, output) => {
|
||||||
let args = [];
|
let args = [];
|
||||||
args.push(`-i "${videoFile}"`);
|
let metaData = [];
|
||||||
|
|
||||||
|
let index = 0;
|
||||||
|
let hasVideo = false;
|
||||||
|
for (let vid of videoAndAudio) {
|
||||||
|
args.push(`-i "${vid.path}"`)
|
||||||
|
if (!hasVideo) {
|
||||||
|
metaData.push(`-map ${index}`)
|
||||||
|
metaData.push(`-metadata:s:a:${index} language=${getLanguageCode(vid.lang, vid.lang)}`)
|
||||||
|
metaData.push(`-metadata:s:v:${index} title="[Funimation]"`)
|
||||||
|
hasVideo = true
|
||||||
|
} else {
|
||||||
|
metaData.push(`-map ${index}:a`)
|
||||||
|
metaData.push(`-metadata:s:a:${index} language=${getLanguageCode(vid.lang, vid.lang)}`)
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let vid of onlyVid) {
|
||||||
|
if (!hasVideo) {
|
||||||
|
args.push(`-i "${vid.path}"`)
|
||||||
|
metaData.push(`-map ${index}`)
|
||||||
|
metaData.push(`-metadata:s:a:${index} language=${getLanguageCode(vid.lang, vid.lang)}`)
|
||||||
|
metaData.push(`-metadata:s:v:${index} title="[Funimation]"`)
|
||||||
|
hasVideo = true
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let aud of onlyAuido) {
|
||||||
|
args.push(`-i "${aud.path}"`)
|
||||||
|
metaData.push(`-map ${index}`)
|
||||||
|
metaData.push(`-metadata:s:a:${index} language=${getLanguageCode(aud.lang, aud.lang)}`)
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
if (audioSettings.uri)
|
|
||||||
args.push(`-i "${audioSettings.uri}"`);
|
|
||||||
for (let index in subtitles) {
|
for (let index in subtitles) {
|
||||||
let sub = subtitles[index];
|
let sub = subtitles[index];
|
||||||
args.push(`-i "${sub.file}"`);
|
args.push(`-i "${sub.file}"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
args.push('-map 0');
|
args.push(...subtitles.map((_, subIndex) => `-map ${subIndex + index}`));
|
||||||
if (audioSettings.uri)
|
args.push(...metaData)
|
||||||
args.push( '-map 1');
|
|
||||||
|
|
||||||
args.push(...subtitles.map((_, index) => `-map ${index + (audioSettings.uri ? 2 : 1)}`));
|
|
||||||
args.push(
|
args.push(
|
||||||
'-metadata:s:v:0 title="[Funimation]"',
|
|
||||||
`-metadata:s:a:0 language=${getLanguageCode(audioSettings.language, argv.sub ? 'jpn' : 'eng')}`,
|
|
||||||
'-c:v copy',
|
'-c:v copy',
|
||||||
'-c:a copy',
|
'-c:a copy',
|
||||||
'-c:s mov_text',
|
'-c:s mov_text',
|
||||||
|
|
@ -58,14 +87,14 @@ const buildCommandMkvMerge = (videoFile, audioSettings, subtitles, output) => {
|
||||||
'--no-audio'
|
'--no-audio'
|
||||||
);
|
);
|
||||||
args.push(`"${videoFile}"`);
|
args.push(`"${videoFile}"`);
|
||||||
args.push(`--language 0:${getLanguageCode(audioSettings.language, argv.sub ? 'jpn' : 'eng')}`);
|
args.push(`--language 0:${getLanguageCode(audioSettings.language, argv.todo ? 'jpn' : 'eng')}`);
|
||||||
args.push(
|
args.push(
|
||||||
'--no-video',
|
'--no-video',
|
||||||
'--audio-tracks 0'
|
'--audio-tracks 0'
|
||||||
);
|
);
|
||||||
args.push(`"${audioSettings.uri}"`);
|
args.push(`"${audioSettings.uri}"`);
|
||||||
} else{
|
} else{
|
||||||
args.push(`--language 1:${argv.sub ? 'jpn' : 'eng'}`);
|
args.push(`--language 1:${argv.todo ? 'jpn' : 'eng'}`);
|
||||||
args.push(
|
args.push(
|
||||||
'--video-tracks 0',
|
'--video-tracks 0',
|
||||||
'--audio-tracks 1'
|
'--audio-tracks 1'
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ const appArgv = (cfg) => {
|
||||||
// init
|
// init
|
||||||
const argv = yargs.parserConfiguration({
|
const argv = yargs.parserConfiguration({
|
||||||
'duplicate-arguments-array': true,
|
'duplicate-arguments-array': true,
|
||||||
'camel-case-expansion': false
|
"camel-case-expansion": false
|
||||||
})
|
})
|
||||||
// main
|
// main
|
||||||
.wrap(Math.min(120)) // yargs.terminalWidth()
|
.wrap(Math.min(120)) // yargs.terminalWidth()
|
||||||
|
|
@ -74,16 +74,10 @@ const appArgv = (cfg) => {
|
||||||
.option('dub', {
|
.option('dub', {
|
||||||
group: 'Downloading:',
|
group: 'Downloading:',
|
||||||
describe: 'Download non-Japanese Dub (English Dub mode by default)',
|
describe: 'Download non-Japanese Dub (English Dub mode by default)',
|
||||||
choices: [ 'enUS', 'esLA', 'ptBR', 'zhMN' ],
|
choices: [ 'enUS', 'esLA', 'ptBR', 'zhMN', 'jpJP' ],
|
||||||
default: cfg.dub || 'enUS',
|
default: cfg.dub || 'enUS',
|
||||||
type: 'array',
|
type: 'array',
|
||||||
})
|
})
|
||||||
.option('sub', {
|
|
||||||
group: 'Downloading:',
|
|
||||||
describe: 'Japanese Dub with subtitles mode (English Dub mode by default)',
|
|
||||||
default: cfg.subsMode || false,
|
|
||||||
type: 'boolean',
|
|
||||||
})
|
|
||||||
.option('subLang', {
|
.option('subLang', {
|
||||||
group: 'Downloading:',
|
group: 'Downloading:',
|
||||||
describe: 'Set the subtitle language (English is default and fallback)',
|
describe: 'Set the subtitle language (English is default and fallback)',
|
||||||
|
|
@ -187,17 +181,10 @@ const appArgv = (cfg) => {
|
||||||
// util
|
// util
|
||||||
.option('nocleanup', {
|
.option('nocleanup', {
|
||||||
group: 'Utilities:',
|
group: 'Utilities:',
|
||||||
describe: 'Move temporary files to trash folder instead of deleting',
|
describe: 'Dont\'t delete the input files after muxing',
|
||||||
default: cfg.noCleanUp || false,
|
default: cfg.noCleanUp || false,
|
||||||
type: 'boolean'
|
type: 'boolean'
|
||||||
})
|
})
|
||||||
.option('notrashfolder', {
|
|
||||||
implies: ['nocleanup'],
|
|
||||||
group: 'Utilities:',
|
|
||||||
describe: 'Don\'t move temporary files to trash folder (Used with --nocleanup)',
|
|
||||||
default: cfg.noTrashFolder || false,
|
|
||||||
type: 'boolean'
|
|
||||||
})
|
|
||||||
// help
|
// help
|
||||||
.option('help', {
|
.option('help', {
|
||||||
alias: 'h',
|
alias: 'h',
|
||||||
|
|
@ -217,8 +204,8 @@ const appArgv = (cfg) => {
|
||||||
|
|
||||||
// Resolve unwanted arrays
|
// Resolve unwanted arrays
|
||||||
for (let key in argv) {
|
for (let key in argv) {
|
||||||
if (argv[key] instanceof Array && !(key === 'subLang' || key === 'dub')) {
|
if (argv[key] instanceof Array && !(key === "subLang" || key === "dub")) {
|
||||||
argv[key] = argv[key].pop();
|
argv[key] = argv[key].pop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return argv;
|
return argv;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue