Merge branch 'master' into master

This commit is contained in:
Jake K 2021-07-05 18:01:59 -04:00 committed by GitHub
commit 370cff37e3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 880 additions and 649 deletions

View file

@ -1,2 +1,2 @@
ffmpeg: "./bin/ffmpeg"
mkvmerge: "./bin/mkvmerge"
ffmpeg: "C:\\Program Files\\ffmpeg\\bin\\ffmpeg.exe"
mkvmerge: "./bin/mkvtoolnix/mkvmerge.exe"

View file

@ -1,6 +1,5 @@
releaseGroup: Funimation
videoLayer: 7
fileSuffix: SIZEp
nServer: 1
mp4mux: false
muxSubs: false

View file

@ -1,2 +1 @@
content: ./videos/
trash: ./videos/_trash/

View file

@ -1,5 +1,7 @@
## Change Log
This changelog is out of date and wont be continued. Please see the releases comments, or if not present the commit comments.
### 4.7.0 (unreleased)
- Change subtitles parser from ttml to vtt
- Improve help command

View file

@ -42,15 +42,20 @@ After installing NodeJS with NPM go to directory with `package.json` file and ty
* `-s <i> -e <s>` sets the show id and episode ids (comma-separated, hyphen-sequence)
* `-q <i>` sets the video layer quality [1...10] (optional, 0 is max)
* `--all` download all videos at once
* `--alt` alternative episode listing (if available)
* `--sub` switch from English dub to Japanese dub with subtitles
* `--sub` select one or more subtile language
* `--dub` select one or more dub languages
* `--simul` force select simulcast version instead of uncut version
* `-x` select server
* `--novids` skip download videos (for downloading subtitles only)
* `--novids` skip download videos
* `--nosubs` skip download subtitles for Dub (if available)
* `--noaudio` skip downloading audio
### Proxy
The proxy is currently unmainted. Use at your on risk.
* `--proxy <s>` http(s)/socks proxy WHATWG url (ex. https://myproxyhost:1080)
* `--proxy-auth <s>` Colon-separated username and password for proxy
* `--ssp` don't use proxy for stream downloading
@ -63,19 +68,17 @@ After installing NodeJS with NPM go to directory with `package.json` file and ty
### Filenaming (optional)
* `-a <s>` release group ("Funimation" by default)
* `-t <s>` show title override
* `--ep <s>` episode number override (ignored in batch mode)
* `--suffix <s>` filename suffix override (first "SIZEp" will be replaced with actual video size, "SIZEp" by default)
* `--fileName` Set the filename template. Use ${variable_name} to insert variables.
You may use 'title', 'episode', 'showTitle', 'season', 'width', 'height' as variables.
### 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
## Filename Template
[`release group`] `title` - `episode` [`suffix`].`extension`
[Funimation] ${showTitle} - ${episode} [${height}p]"]
## CLI Examples

961
funi.js

File diff suppressed because it is too large Load diff

View file

@ -44,7 +44,6 @@ const nodeVer = 'node14-';
fs.mkdirSync(`${buildDir}/bin`);
fs.mkdirSync(`${buildDir}/config`);
fs.mkdirSync(`${buildDir}/videos`);
fs.mkdirSync(`${buildDir}/videos/_trash`);
fs.copySync('./bin/', `${buildDir}/bin/`);
fs.copySync('./config/bin-path.yml', `${buildDir}/config/bin-path.yml`);
fs.copySync('./config/cli-defaults.yml', `${buildDir}/config/cli-defaults.yml`);

155
modules/merger.js Normal file
View file

@ -0,0 +1,155 @@
const iso639 = require('iso-639');
const argv = require('../funi').argv;
/**
* @param {Array<object>} videoAndAudio
* @param {Array<object>} onlyVid
* @param {Array<object>} onlyAudio
* @param {Array<object>} subtitles
* @param {string} output
* @returns {string}
*/
const buildCommandFFmpeg = (videoAndAudio, onlyVid, onlyAudio, subtitles, output) => {
let args = [];
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 onlyAudio) {
args.push(`-i "${aud.path}"`);
metaData.push(`-map ${index}`);
metaData.push(`-metadata:s:a:${index} language=${getLanguageCode(aud.lang, aud.lang)}`);
index++;
}
for (let index in subtitles) {
let sub = subtitles[index];
args.push(`-i "${sub.file}"`);
}
args.push(...metaData);
args.push(...subtitles.map((_, subIndex) => `-map ${subIndex + index}`));
args.push(
'-c:v copy',
'-c:a copy'
);
args.push(output.split('.').pop().toLowerCase() === 'mp4' ? '-c:s mov_text' : '-c:s ass');
args.push(...subtitles.map((sub, index) => `-metadata:s:${index + 2} language=${getLanguageCode(sub.language)}`));
args.push(`"${output}"`);
return args.join(' ');
};
/**
* @param {string} videoFile
* @param {object} audioFile
* @param {Array<object>} subtitles
* @returns {string}
*/
const buildCommandMkvMerge = (videoAndAudio, onlyVid, onlyAudio, subtitles, output) => {
let args = [];
let hasVideo = false;
args.push(`-o "${output}"`);
args.push(
'--no-date',
'--disable-track-statistics-tags',
'--engage no_variable_data',
);
for (let vid of onlyVid) {
if (!hasVideo) {
args.push(
'--video-tracks 0',
'--no-audio'
);
args.push('--track-name 0:[Funimation]');
hasVideo = true;
args.push(`"${vid.path}"`);
}
}
for (let vid of videoAndAudio) {
console.log(vid, vid.lang);
if (!hasVideo) {
args.push(
'--video-tracks 0',
'--audio-tracks 1'
);
args.push('--track-name 0:[Funimation]');
args.push(`--language 1:${getLanguageCode(vid.lang, vid.lang)}`);
hasVideo = true;
} else {
args.push(
'--no-video',
'--audio-tracks 1'
);
args.push(`--language 1:${getLanguageCode(vid.lang, vid.lang)}`);
}
args.push(`"${vid.path}"`);
}
for (let aud of onlyAudio) {
args.push(`--language 0:${getLanguageCode(aud.lang, aud.lang)}`);
args.push(
'--no-video',
'--audio-tracks 0'
);
args.push(`"${aud.path}"`);
}
if(subtitles.length > 0){
for (let subObj of subtitles) {
args.push('--language',`0:${getLanguageCode(subObj.language)}`);
args.push(`"${subObj.file}"`);
}
} else {
args.push(
'--no-subtitles',
'--no-attachments'
);
}
return args.join(' ');
};
const getLanguageCode = (from, _default = 'eng') => {
for (let lang in iso639.iso_639_2) {
let langObj = iso639.iso_639_2[lang];
if (Object.prototype.hasOwnProperty.call(langObj, '639-1') && langObj['639-1'] === from) {
return langObj['639-2'];
}
}
return _default;
};
module.exports = {
buildCommandFFmpeg,
getLanguageCode,
buildCommandMkvMerge
};

View file

@ -1,193 +1,214 @@
const yargs = require('yargs');
const appArgv = (cfg, langsData) => {
const availableFilenameVars = [
'title',
'episode',
'showTitle',
'season',
'width',
'height'
];
const appArgv = (cfg) => {
// init
return yargs.parserConfiguration({
"duplicate-arguments-array": false,
const argv = yargs.parserConfiguration({
'duplicate-arguments-array': true,
'camel-case-expansion': false
})
// main
.wrap(Math.min(120)) // yargs.terminalWidth()
.help(false).version(false)
.usage('Usage: $0 [options]')
.wrap(Math.min(120)) // yargs.terminalWidth()
.help(false).version(false)
.usage('Usage: $0 [options]')
// auth
.option('auth', {
group: 'Authentication:',
describe: 'Enter authentication mode',
type: 'boolean',
})
.option('auth', {
group: 'Authentication:',
describe: 'Enter authentication mode',
type: 'boolean',
})
// search
.option('search', {
alias: 'f',
group: 'Search:',
describe: 'Search show ids',
type: 'string',
})
.option('search', {
alias: 'f',
group: 'Search:',
describe: 'Search show ids',
type: 'string',
})
// select show and eps
.option('s', {
group: 'Downloading:',
describe: 'Sets the show id',
type: 'number',
})
.option('e', {
group: 'Downloading:',
describe: 'Select episode ids (comma-separated, hyphen-sequence)',
type: 'string',
})
.option('s', {
group: 'Downloading:',
describe: 'Sets the show id',
type: 'number',
})
.option('e', {
group: 'Downloading:',
describe: 'Select episode ids (comma-separated, hyphen-sequence)',
type: 'string',
})
.option('all', {
group: 'Downloading:',
describe: 'Used to download all episodes from the show',
type: 'boolean',
default: cfg.all || false
})
.option('partsize', {
group: 'Downloading:',
describe: 'The amount of parts that should be downloaded in paralell',
type: 'number',
default: cfg.partsize || 10
})
// quality
.option('q', {
group: 'Downloading:',
describe: 'Select video layer (0 is max)',
choices: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
default: cfg.videoLayer || 7,
type: 'number',
})
.option('q', {
group: 'Downloading:',
describe: 'Select video layer (0 is max)',
choices: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
default: cfg.videoLayer || 7,
type: 'number',
})
// alt listing
.option('alt', {
group: 'Downloading:',
describe: 'Alternative episode listing (if available)',
default: cfg.altList || false,
type: 'boolean',
})
.option('alt', {
group: 'Downloading:',
describe: 'Alternative episode listing (if available)',
default: cfg.altList || false,
type: 'boolean',
})
// switch to subs
.option('dub', {
group: 'Downloading:',
describe: 'Download non-Japanese Dub (English Dub mode by default)',
choices: [ 'enUS', 'esLA', 'ptBR' ],
default: cfg.dub || 'enUS',
type: 'string',
})
.option('sub', {
group: 'Downloading:',
describe: 'Japanese Dub with subtitles mode (English Dub mode by default)',
default: cfg.subsMode || false,
type: 'boolean',
})
.option('dub', {
group: 'Downloading:',
describe: 'Download non-Japanese Dub (English Dub mode by default)',
choices: [ 'enUS', 'esLA', 'ptBR', 'zhMN', 'jaJP' ],
default: cfg.dub || 'enUS',
type: 'array',
})
.option('subLang', {
group: 'Downloading:',
describe: 'Set the subtitle language (English is default and fallback)',
default: cfg.subLang || 'enUS',
choices: [ 'enUS', 'esLA', 'ptBR' ],
type: 'array'
})
.option('fontSize', {
group: 'Downloading:',
describe: 'Used to set the fontsize of the subtitles',
default: cfg.fontSize || 55,
type: 'number'
})
// simulcast
.option('simul', {
group: 'Downloading:',
describe: 'Force downloading simulcast ver. instead of uncut ver. (if uncut ver. available)',
default: cfg.forceSimul || false,
type: 'boolean',
})
.option('simul', {
group: 'Downloading:',
describe: 'Force downloading simulcast ver. instead of uncut ver. (if uncut ver. available)',
default: cfg.forceSimul || false,
type: 'boolean',
})
// server number
.option('x', {
alias: 'server',
group: 'Downloading:',
describe: 'Select server',
choices: [1, 2, 3, 4],
default: cfg.nServer || 1,
type: 'number',
})
.option('x', {
alias: 'server',
group: 'Downloading:',
describe: 'Select server',
choices: [1, 2, 3, 4],
default: cfg.nServer || 1,
type: 'number',
})
// skip
.option('novids', {
group: 'Downloading:',
alias: 'skipdl',
describe: 'Skip downloading video (for downloading subtitles only)',
type: 'boolean',
})
.option('nosubs', {
group: 'Downloading:',
describe: 'Skip downloading subtitles for English Dub (if available)',
type: 'boolean',
})
.option('noaudio', {
group: 'Downloading:',
describe: 'Skip downloading audio',
type: 'boolean'
})
.option('novids', {
group: 'Downloading:',
alias: 'skipdl',
describe: 'Skip downloading video',
type: 'boolean',
})
.option('nosubs', {
group: 'Downloading:',
describe: 'Skip downloading subtitles for English Dub (if available)',
type: 'boolean',
default: false
})
// proxy
.option('proxy', {
group: 'Proxy:',
describe: 'Set http(s)/socks proxy WHATWG url',
default: cfg.proxy || false,
hidden: true,
})
.option('proxy-auth', {
group: 'Proxy:',
describe: 'Colon-separated username and password for proxy',
default: cfg.proxy_auth || false,
hidden: true,
})
.option('ssp', {
group: 'Proxy:',
describe: 'Don\'t use proxy for stream and subtitles downloading',
default: cfg.proxy_ssp || false,
hidden: true,
type: 'boolean',
})
.option('proxy', {
group: 'Proxy:',
describe: 'Set http(s)/socks proxy WHATWG url',
default: cfg.proxy || false,
hidden: true,
})
.option('proxy-auth', {
group: 'Proxy:',
describe: 'Colon-separated username and password for proxy',
default: cfg.proxy_auth || false,
hidden: true,
})
.option('ssp', {
group: 'Proxy:',
describe: 'Don\'t use proxy for stream and subtitles downloading',
default: cfg.proxy_ssp || false,
hidden: true,
type: 'boolean',
})
// muxing
.option('skipmux', {
group: 'Muxing:',
describe: 'Skip muxing video and subtitles',
type: 'boolean',
})
.option('mp4', {
group: 'Muxing:',
describe: 'Mux into mp4',
default: cfg.mp4mux || false,
type: 'boolean'
})
.option('mks', {
group: 'Muxing:',
describe: 'Add subtitles to mkv/mp4 (if available)',
default: cfg.muxSubs || false,
type: 'boolean'
})
.option('skipmux', {
group: 'Muxing:',
describe: 'Skip muxing video and subtitles',
type: 'boolean',
})
.option('mp4', {
group: 'Muxing:',
describe: 'Mux into mp4',
default: cfg.mp4mux || false,
type: 'boolean'
})
// filenaming
.option('a', {
alias: 'grouptag',
group: 'Filename Template:',
describe: 'Release group',
default: cfg.releaseGroup || 'Funimation',
type: 'string'
})
.option('t', {
alias: 'title',
group: 'Filename Template:',
describe: 'Series title override',
type: 'string'
})
.option('ep', {
group: 'Filename Template:',
describe: 'Episode number override (ignored in batch mode)',
type: 'string'
})
.option('suffix', {
group: 'Filename Template:',
describe: 'Filename suffix override (first "SIZEp" will be replaced with actual video size)',
default: cfg.fileSuffix || 'SIZEp',
type: 'string'
})
.option('fileName', {
group: 'Filename Template:',
describe: `Set the filename template. Use \${variable_name} to insert variables.\nYou may use ${availableFilenameVars
.map(a => `'${a}'`).join(', ')} as variables.`,
type: 'string',
default: cfg.fileName || '[Funimation] ${showTitle} - ${episode} [${height}p]'
})
.option('numbers', {
group: 'Filename Template:',
describe: `Set how long a number in the title should be at least.\n${[[3, 5, '005'], [2, 1, '01'], [1, 20, '20']]
.map(val => `Set in config: ${val[0]}; Episode number: ${val[1]}; Output: ${val[2]}`).join('\n')}`,
type: 'number',
default: cfg.numbers || 2
})
// util
.option('nocleanup', {
group: 'Utilities:',
describe: 'Move temporary files to trash folder instead of deleting',
default: cfg.noCleanUp || false,
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'
})
.option('nocleanup', {
group: 'Utilities:',
describe: 'Dont\'t delete the input files after muxing',
default: cfg.noCleanUp || false,
type: 'boolean'
})
// help
.option('help', {
alias: 'h',
group: 'Help:',
describe: 'Show this help',
type: 'boolean'
})
.option('help', {
alias: 'h',
group: 'Help:',
describe: 'Show this help',
type: 'boolean'
})
// usage
.example([
['$0 --search "My Hero"', 'search "My Hero" in title'],
['$0 -s 124389 -e 1,2,3', 'download episodes 1-3 from show with id 124389'],
['$0 -s 124389 -e 1-3,2-7,s1-2', 'download episodes 1-7 and "S"-episodes 1-2 from show with id 124389'],
])
.example([
['$0 --search "My Hero"', 'search "My Hero" in title'],
['$0 -s 124389 -e 1,2,3', 'download episodes 1-3 from show with id 124389'],
['$0 -s 124389 -e 1-3,2-7,s1-2', 'download episodes 1-7 and "S"-episodes 1-2 from show with id 124389'],
])
// --
.argv;
}
.argv;
// Resolve unwanted arrays
for (let key in argv) {
if (argv[key] instanceof Array && !(key === 'subLang' || key === 'dub')) {
argv[key] = argv[key].pop();
}
}
return argv;
};
const showHelp = yargs.showHelp;
module.exports = {
appArgv,
showHelp
showHelp,
availableFilenameVars
};

View file

@ -4,7 +4,7 @@ const existsFile = fs.existsSync;
const loadYamlFile = (file) => {
return yaml.parse(fs.readFileSync(file, 'utf8'));
}
};
const loadYamlCfg = (file) => {
if(existsFile(`${file}.user.yml`)){
@ -21,6 +21,6 @@ const loadYamlCfg = (file) => {
}
}
return {};
}
};
module.exports = loadYamlCfg;

View file

@ -10,7 +10,7 @@ const getData = async (options) => {
}
};
if(options.responseType) {
gOptions.responseType = options.responseType
gOptions.responseType = options.responseType;
}
if(options.baseUrl){
gOptions.prefixUrl = options.baseUrl;
@ -71,6 +71,6 @@ const getData = async (options) => {
error,
};
}
}
};
module.exports = getData;

View file

@ -35,10 +35,10 @@ function loadVtt(vttStr) {
}
// ass specific
function convertToAss(vttStr){
function convertToAss(vttStr, lang, fontSize){
let ass = [
'\ufeff[Script Info]',
'Title: English',
`Title: ${lang}`,
'ScriptType: v4.00+',
'PlayResX: 1280',
'PlayResY: 720',
@ -49,8 +49,8 @@ function convertToAss(vttStr){
'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',
`Style: Main,Noto Sans,${fontSize},&H00FFFFFF,&H000000FF,&H00020713,&H00000000,0,0,0,0,100,100,0,0,1,3,0,2,10,10,10,1`,
`Style: MainTop,Noto Sans,${fontSize},&H00FFFFFF,&H000000FF,&H00020713,&H00000000,0,0,0,0,100,100,0,0,1,3,0,8,10,10,10`,
'',
'[Events]',
'Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text',
@ -141,7 +141,7 @@ function toSubsTime(str, srtFormat) {
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 = x[0]*60*60 + x[1]*60 + x[2] + Number(x[3]);
sx = sx.toFixed(msLen).split('.');
@ -160,7 +160,7 @@ function padTimeNum(sep, input, pad){
}
// export module
module.exports = (vttStr, toSrt) => {
module.exports = (vttStr, toSrt, lang = 'English', fontSize) => {
const convert = toSrt ? convertToSrt : convertToAss;
return convert(vttStr);
return convert(vttStr, lang, fontSize);
};

View file

@ -1,7 +1,7 @@
{
"name": "funimation-downloader-nx",
"short_name": "funi",
"version": "4.8.0-beta.1",
"version": "4.9.1",
"description": "Download videos from Funimation via cli.",
"keywords": [
"download",
@ -12,21 +12,21 @@
"utility",
"cli"
],
"author": "AniDL",
"homepage": "https://github.com/anidl/funimation-downloader-nx",
"author": "AniDL/Izu-co",
"homepage": "https://github.com/izu-co/funimation-downloader-nx",
"repository": {
"type": "git",
"url": "https://github.com/anidl/funimation-downloader-nx.git"
"url": "https://github.com/izu-co/funimation-downloader-nx.git"
},
"bugs": {
"url": "https://github.com/anidl/funimation-downloader-nx/issues"
"url": "https://github.com/izu-co/funimation-downloader-nx/issues"
},
"license": "MIT",
"main": "funi.js",
"dependencies": {
"form-data": "^3.0.0",
"got": "^11.7.0",
"hls-download": "^2.5.3",
"iso-639": "^0.2.2",
"lookpath": "^1.1.0",
"m3u8-parsed": "^1.3.0",
"sei-helper": "^3.3.0",

View file