Merge pull request #499 from anidl/4.2.0

4.2.0 - Bunch of fixes and additions
This commit is contained in:
AnimeDL 2023-07-13 15:27:38 -07:00 committed by GitHub
commit 88d5c11032
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 194 additions and 40 deletions

View file

@ -14,9 +14,11 @@ export type CrunchyDownloadOptions = {
partsize: number,
callbackMaker?: (data: DownloadInfo) => HLSCallback,
timeout: number,
waittime: number,
fsRetryTime: number,
dlsubs: string[],
skipsubs: boolean,
nosubs?: boolean,
mp4: boolean,
override: string[],
videoTitle: string,
@ -40,6 +42,7 @@ export type CurnchyMultiDownload = {
export type CrunchyMuxOptions = {
output: string,
skipSubMux?: boolean
keepAllVideos?: bolean
novids?: boolean,
mp4: boolean,
forceMuxer?: 'ffmpeg'|'mkvmerge',

View file

@ -53,7 +53,7 @@ export default class Crunchy implements ServiceClass {
constructor(private debug = false) {
this.cfg = yamlCfg.loadCfg();
this.token = yamlCfg.loadCRToken();
this.req = new reqModule.Req(domain, false, false, 'cr');
this.req = new reqModule.Req(domain, debug, false, 'cr');
}
public checkToken(): boolean {
@ -144,6 +144,16 @@ export default class Crunchy implements ServiceClass {
}
}
return true;
} else if (argv.extid) {
await this.refreshToken();
const selected = await this.getObjectById(argv.extid, false, true);
for (const select of selected as Partial<CrunchyEpMeta>[]) {
if (!(await this.downloadEpisode(select as CrunchyEpMeta, {...argv, skipsubs: false }))) {
console.error(`Unable to download selected episode ${select.episodeNumber}`);
return false;
}
}
return true;
}
else{
console.info('No option selected or invalid value entered. Try --help.');
@ -228,7 +238,7 @@ export default class Crunchy implements ServiceClass {
yamlCfg.saveCRToken(this.token);
}
public async getProfile() : Promise<boolean> {
public async getProfile(silent = false) : Promise<boolean> {
if(!this.token.access_token){
console.error('No access token!');
return false;
@ -245,10 +255,18 @@ export default class Crunchy implements ServiceClass {
return false;
}
const profile = JSON.parse(profileReq.res.body);
console.info('USER: %s (%s)', profile.username, profile.email);
if (!silent) {
console.info('USER: %s (%s)', profile.username, profile.email);
}
return true;
}
public sleep(ms: number) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
public async refreshToken(ifNeeded = false, silent = false) {
if(!this.token.access_token && !this.token.refresh_token || this.token.access_token && !this.token.refresh_token){
await this.doAnonymousAuth();
@ -281,19 +299,25 @@ export default class Crunchy implements ServiceClass {
yamlCfg.saveCRToken(this.token);
}
if(this.token.refresh_token) {
if (!silent)
await this.getProfile();
await this.getProfile(silent);
} else {
console.info('USER: Anonymous');
}
await this.getCMStoken();
await this.getCMStoken(ifNeeded);
}
public async getCMStoken(){
public async getCMStoken(ifNeeded = false) {
if(!this.token.access_token){
console.error('No access token!');
return;
}
if (ifNeeded && this.cmsToken.cms) {
if (!(Date.now() >= new Date(this.cmsToken.cms.expires).getTime())) {
return;
}
}
const cmsTokenReqOpts = {
headers: {
Authorization: `Bearer ${this.token.access_token}`,
@ -847,22 +871,57 @@ export default class Crunchy implements ServiceClass {
downloaded({
service: 'crunchy',
type: 's'
}, data.showID, [data.episodeNumber]);
}, data.showID, [data.episodeNumber || data.e]);
}
return true;
}
public async getObjectById(e?: string, earlyReturn?: boolean): Promise<ObjectInfo|Partial<CrunchyEpMeta>[]|undefined> {
public async getObjectById(e?: string, earlyReturn?: boolean, external_id?: boolean): Promise<ObjectInfo|Partial<CrunchyEpMeta>[]|undefined> {
if(!this.cmsToken.cms){
console.error('Authentication required!');
return;
return [];
}
let convertedObjects;
if (external_id) {
const epFilter = parseSelect(e as string);
const objectIds = [];
for (const ob of epFilter.values) {
const extIdReqOpts = [
api.beta_cms,
this.cmsToken.cms.bucket,
'/channels/crunchyroll/objects',
'?',
new URLSearchParams({
'external_id': ob,
'Policy': this.cmsToken.cms.policy,
'Signature': this.cmsToken.cms.signature,
'Key-Pair-Id': this.cmsToken.cms.key_pair_id,
}),
].join('');
const extIdReq = await this.req.getData(extIdReqOpts);
if (!extIdReq.ok || !extIdReq.res) {
console.error('Objects Request FAILED!');
if (extIdReq.error && extIdReq.error.res && extIdReq.error.res.body) {
console.info('[INFO] Body:', extIdReq.error.res.body);
}
continue;
}
const oldObjectInfo = JSON.parse(extIdReq.res.body) as Record<any, any>;
for (const object of oldObjectInfo.items) {
objectIds.push(object.id);
}
}
convertedObjects = objectIds.join(',');
}
const doEpsFilter = parseSelect(e as string);
const doEpsFilter = parseSelect(convertedObjects ?? e as string);
if(doEpsFilter.values.length < 1){
console.info('\nObjects not selected!\n');
return;
return [];
}
// node index.js --service crunchy -e G6497Z43Y,GRZXCMN1W,G62PEZ2E6,G25FVGDEK,GZ7UVPVX5
@ -885,7 +944,7 @@ export default class Crunchy implements ServiceClass {
objectInfo.error = true;
return objectInfo;
}
return;
return [];
}
const objectInfo = JSON.parse(objectReq.res.body) as ObjectInfo;
@ -951,7 +1010,7 @@ export default class Crunchy implements ServiceClass {
await this.logObject(item, 2);
}
console.info('');
return selectedMedia;
return selectedMedia;
}
public async downloadMediaList(medias: CrunchyEpMeta, options: CrunchyDownloadOptions) : Promise<{
@ -1311,6 +1370,11 @@ export default class Crunchy implements ServiceClass {
console.warn('Subtitles downloading disabled for hardsubs streams.');
options.skipsubs = true;
}
if (options.nosubs) {
console.info('Subtitles downloading disabled from nosubs flag.');
options.skipsubs = true;
}
if(!options.skipsubs && options.dlsubs.indexOf('none') == -1){
if(pbData.meta.subtitles && Object.values(pbData.meta.subtitles).length > 0){
@ -1365,6 +1429,7 @@ export default class Crunchy implements ServiceClass {
else{
console.info('Subtitles downloading skipped!');
}
await this.sleep(options.waittime);
}
return {
error: dlFailed,
@ -1392,6 +1457,7 @@ export default class Crunchy implements ServiceClass {
};
}),
simul: false,
keepAllVideos: options.keepAllVideos,
fonts: Merger.makeFontsList(this.cfg.dir.fonts, data.filter(a => a.type === 'Subtitle') as sxItem[]),
videoAndAudio: data.filter(a => a.type === 'Video').map((a) : MergerInput => {
if (a.type === 'Subtitle')
@ -1465,7 +1531,7 @@ export default class Crunchy implements ServiceClass {
//Iterate over episode versions for audio languages
for (const version of episode.versions) {
//Make sure there is only one of the same language
if (!item.langs.find(a => a.cr_locale == version.audio_locale)) {
if (!item.langs.find(a => a?.cr_locale == version.audio_locale)) {
//Push to arrays if there is no duplicates of the same language.
item.items.push(episode);
item.langs.push(langsData.languages.find(a => a.cr_locale == version.audio_locale) as langsData.LanguageItem);
@ -1475,7 +1541,7 @@ export default class Crunchy implements ServiceClass {
//Episode didn't have versions, mark it as such to be logged.
serieshasversions = false;
//Make sure there is only one of the same language
if (!item.langs.find(a => a.cr_locale == episode.audio_locale)) {
if (!item.langs.find(a => a?.cr_locale == episode.audio_locale)) {
//Push to arrays if there is no duplicates of the same language.
item.items.push(episode);
item.langs.push(langsData.languages.find(a => a.cr_locale == episode.audio_locale) as langsData.LanguageItem);
@ -1507,7 +1573,7 @@ export default class Crunchy implements ServiceClass {
item.items.find(a => !a.season_title.match(/\(\w+ Dub\)/))?.season_title ?? item.items[0].season_title.replace(/\(\w+ Dub\)/g, '').trimEnd()
} - Season ${item.items[0].season_number} - ${item.items[0].title} [${
item.items.map((a, index) => {
return `${a.is_premium_only ? '☆ ' : ''}${item.langs[index].name}`;
return `${a.is_premium_only ? '☆ ' : ''}${item.langs[index]?.name ?? 'Unknown'}`;
}).join(', ')
}]`);
}
@ -1523,7 +1589,7 @@ export default class Crunchy implements ServiceClass {
const seconds = Math.floor(value.items[0].duration_ms / 1000);
return {
e: key.startsWith('E') ? key.slice(1) : key,
lang: value.langs.map(a => a.code),
lang: value.langs.map(a => a?.code),
name: value.items[0].title,
season: value.items[0].season_number.toString(),
seriesTitle: value.items[0].series_title.replace(/\(\w+ Dub\)/g, '').trimEnd(),
@ -1565,7 +1631,7 @@ export default class Crunchy implements ServiceClass {
for (const key of Object.keys(eps)) {
const itemE = eps[key];
itemE.items.forEach((item, index) => {
if (!dubLang.includes(itemE.langs[index].code))
if (!dubLang.includes(itemE.langs[index]?.code))
return;
item.hide_season_title = true;
if(item.season_title == '' && item.series_title != ''){

View file

@ -1,4 +1,4 @@
# multi-downloader-nx (4.1.0v)
# multi-downloader-nx (4.2.0v)
If you find any bugs in this documentation or in the programm itself please report it [over on GitHub](https://github.com/anidl/multi-downloader-nx/issues).
@ -77,7 +77,7 @@ The output is organized in pages. Use this command to output the items for the g
#### `--search-locale`
| **Service** | **Usage** | **Type** | **Required** | **Alias** | **Choices** | **Default** |**cli-default Entry**
| --- | --- | --- | --- | --- | --- | --- | ---|
| Crunchyroll | `--search-locale ${locale}` | `string` | `No`| `NaN` | [`''`, `en-US`, `en-IN`, `es-LA`, `es-419`, `es-ES`, `pt-BR`, `pt-PT`, `fr-FR`, `de-DE`, `ar-ME`, `ar-SA`, `it-IT`, `ru-RU`, `tr-TR`, `hi-IN`, `zh-CN`, `ko-KR`] | ``| `search-locale: ` |
| Crunchyroll | `--search-locale ${locale}` | `string` | `No`| `NaN` | [`''`, `en-US`, `en-IN`, `es-LA`, `es-419`, `es-ES`, `pt-BR`, `pt-PT`, `fr-FR`, `de-DE`, `ar-ME`, `ar-SA`, `it-IT`, `ru-RU`, `tr-TR`, `hi-IN`, `zh-CN`, `zh-TW`, `ko-KR`, `ja-JP`, `ca-ES`, `pl-PL`, `th-TH`, `ta-IN`, `ms-MY`] | ``| `search-locale: ` |
Set the search local that will be used for searching for items.
#### `--new`
@ -114,6 +114,13 @@ Used to set the season ID to download from
Set the episode(s) to download from any given show.
For multiple selection: 1-4 OR 1,2,3,4
For special episodes: S1-4 OR S1,S2,S3,S4 where S is the special letter
#### `--extid`
| **Service** | **Usage** | **Type** | **Required** | **Alias** | **cli-default Entry**
| --- | --- | --- | --- | --- | ---|
| Crunchyroll | `--extid ${selection}` | `string` | `No`| `--externalid` | `NaN` |
Set the external id to lookup/download.
Allows you to download or view legacy Crunchyroll Ids
#### `-q`
| **Service** | **Usage** | **Type** | **Required** | **Alias** | **Default** |**cli-default Entry**
| --- | --- | --- | --- | --- | --- | ---|
@ -157,17 +164,17 @@ Select specific stream
#### `--hslang`
| **Service** | **Usage** | **Type** | **Required** | **Alias** | **Choices** | **Default** |**cli-default Entry**
| --- | --- | --- | --- | --- | --- | --- | ---|
| Crunchyroll | `--hslang ${hslang}` | `string` | `No`| `NaN` | [`none`, `en`, `en-IN`, `es-419`, `es-ES`, `pt-BR`, `pt-PT`, `fr`, `de`, `ar`, `it`, `ru`, `tr`, `hi`, `zh`, `zh-CN`, `ko`, `ja`] | `none`| `hslang: ` |
| Crunchyroll | `--hslang ${hslang}` | `string` | `No`| `NaN` | [`none`, `en`, `en-IN`, `es-419`, `es-ES`, `pt-BR`, `pt-PT`, `fr`, `de`, `ar`, `it`, `ru`, `tr`, `hi`, `zh`, `zh-CN`, `zh-TW`, `ko`, `ja`, `ca-ES`, `pl-PL`, `th-TH`, `ta-IN`, `ms-MY`, `un`] | `none`| `hslang: ` |
Download video with specific hardsubs
#### `--dlsubs`
| **Service** | **Usage** | **Type** | **Required** | **Alias** | **Choices** | **Default** |**cli-default Entry**
| --- | --- | --- | --- | --- | --- | --- | ---|
| All | `--dlsubs ${sub1} ${sub2}` | `array` | `No`| `NaN` | [`all`, `none`, `en`, `en-IN`, `es-419`, `es-ES`, `pt-BR`, `pt-PT`, `fr`, `de`, `ar`, `it`, `ru`, `tr`, `hi`, `zh`, `zh-CN`, `ko`, `ja`] | `all`| `dlsubs: ` |
| All | `--dlsubs ${sub1} ${sub2}` | `array` | `No`| `NaN` | [`all`, `none`, `en`, `en-IN`, `es-419`, `es-ES`, `pt-BR`, `pt-PT`, `fr`, `de`, `ar`, `it`, `ru`, `tr`, `hi`, `zh`, `zh-CN`, `zh-TW`, `ko`, `ja`, `ca-ES`, `pl-PL`, `th-TH`, `ta-IN`, `ms-MY`, `un`] | `all`| `dlsubs: ` |
Download subtitles by language tag (space-separated)
Funi Only: zh
Crunchy Only: en-IN, es-419, es-ES, pt-PT, fr, de, ar, ar, it, ru, tr, hi, zh-CN, ko
Crunchy Only: en-IN, es-419, es-ES, pt-PT, fr, de, ar, ar, it, ru, tr, hi, zh-CN, zh-TW, ko, ca-ES, pl-PL, th-TH, ta-IN, ms-MY
#### `--novids`
| **Service** | **Usage** | **Type** | **Required** | **Alias** | **cli-default Entry**
| --- | --- | --- | --- | --- | ---|
@ -189,11 +196,11 @@ Skip downloading subtitles
#### `--dubLang`
| **Service** | **Usage** | **Type** | **Required** | **Alias** | **Choices** | **Default** |**cli-default Entry**
| --- | --- | --- | --- | --- | --- | --- | ---|
| All | `--dubLang ${dub1} ${dub2}` | `array` | `No`| `NaN` | [`eng`, `spa`, `spa-419`, `spa-ES`, `por`, `fra`, `deu`, `ara-ME`, `ara`, `ita`, `rus`, `tur`, `hin`, `cmn`, `zho`, `kor`, `jpn`] | `jpn`| `dubLang: ` |
| All | `--dubLang ${dub1} ${dub2}` | `array` | `No`| `NaN` | [`eng`, `spa`, `spa-419`, `spa-ES`, `por`, `fra`, `deu`, `ara-ME`, `ara`, `ita`, `rus`, `tur`, `hin`, `cmn`, `zho`, `chi`, `kor`, `jpn`, `cat`, `pol`, `tha`, `tam`, `may`, `und`] | `und`| `dubLang: ` |
Set the language to download:
Funi Only: cmn
Crunchy Only: eng, spa-419, spa-ES, por, fra, deu, ara-ME, ara, ita, rus, tur, hin, zho, kor
Crunchy Only: eng, spa-419, spa-ES, por, fra, deu, ara-ME, ara, ita, rus, tur, hin, zho, chi, kor, cat, pol, tha, tam, may
#### `--all`
| **Service** | **Usage** | **Type** | **Required** | **Alias** | **Default** |**cli-default Entry**
| --- | --- | --- | --- | --- | --- | ---|
@ -218,6 +225,12 @@ If selected, all available dubs will get downloaded
| All | `--timeout ${timeout}` | `number` | `No`| `NaN` | `15000`| `timeout: ` |
Set the timeout of all download reqests. Set in millisecods
#### `--waittime`
| **Service** | **Usage** | **Type** | **Required** | **Alias** | **Default** |**cli-default Entry**
| --- | --- | --- | --- | --- | --- | ---|
| Crunchyroll, Hidive | `--waittime ${waittime}` | `number` | `No`| `NaN` | `0`| `waittime: ` |
Set the time the program waits between downloads. Set in millisecods
#### `--simul`
| **Service** | **Usage** | **Type** | **Required** | **Alias** | **Default** |**cli-default Entry**
| --- | --- | --- | --- | --- | --- | ---|
@ -268,6 +281,12 @@ If a file already exists, the tool will ask you how to proceed. With this, you c
| All | `--mp4 ` | `boolean` | `No`| `NaN` | `false`| `mp4: ` |
If selected, the output file will be an mp4 file (not recommended tho)
#### `--keepAllVideos`
| **Service** | **Usage** | **Type** | **Required** | **Alias** | **Default** |**cli-default Entry**
| --- | --- | --- | --- | --- | --- | ---|
| Crunchyroll, Hidive | `--keepAllVideos ` | `boolean` | `No`| `NaN` | `false`| `keepAllVideos: ` |
If set to true, it will keep all videos in the merge process, rather than discarding the extra videos.
#### `--skipmux`
| **Service** | **Usage** | **Type** | **Required** | **Alias** | **cli-default Entry**
| --- | --- | --- | --- | --- | ---|
@ -316,14 +335,14 @@ Set the options given to ffmpeg
| All | `--defaultAudio ${args}` | `string` | `No`| `NaN` | `eng`| `defaultAudio: ` |
Set the default audio track by language code
Possible Values: eng, eng, spa, spa-419, spa-ES, por, por, fra, deu, ara-ME, ara, ita, rus, tur, hin, cmn, zho, kor, jpn
Possible Values: eng, eng, spa, spa-419, spa-ES, por, por, fra, deu, ara-ME, ara, ita, rus, tur, hin, cmn, zho, chi, kor, jpn, cat, pol, tha, tam, may, und
#### `--defaultSub`
| **Service** | **Usage** | **Type** | **Required** | **Alias** | **Default** |**cli-default Entry**
| --- | --- | --- | --- | --- | --- | ---|
| All | `--defaultSub ${args}` | `string` | `No`| `NaN` | `eng`| `defaultSub: ` |
Set the default subtitle track by language code
Possible Values: eng, eng, spa, spa-419, spa-ES, por, por, fra, deu, ara-ME, ara, ita, rus, tur, hin, cmn, zho, kor, jpn
Possible Values: eng, eng, spa, spa-419, spa-ES, por, por, fra, deu, ara-ME, ara, ita, rus, tur, hin, cmn, zho, chi, kor, jpn, cat, pol, tha, tam, may, und
### Filename Template
#### `--fileName`
| **Service** | **Usage** | **Type** | **Required** | **Alias** | **Default** |**cli-default Entry**

View file

@ -6,6 +6,7 @@ import open from 'open';
import path from 'path';
import { PublicWebSocket } from './websocket';
import { console } from '../../modules/log';
import packageJson from '../../package.json';
process.title = 'AniDL';
@ -21,6 +22,8 @@ app.use(express.json());
app.use(cors());
app.use(express.static(path.join(workingDir, 'gui', 'server', 'build'), { maxAge: 1000 * 60 * 20 }));
console.info(`\n=== Multi Downloader NX GUI ${packageJson.version} ===\n`);
const server = app.listen(cfg.gui.port, () => {
console.info(`GUI server started on port ${cfg.gui.port}`);
});

View file

@ -59,7 +59,7 @@ export default class Hidive implements ServiceClass {
this.session = yamlCfg.loadHDSession();
this.token = yamlCfg.loadHDToken();
this.client = yamlCfg.loadHDProfile() as {ipAddress: string, xNonce: string, xSignature: string, visitId: string, profile: {userId: number, profileId: number, deviceId : string}};
this.req = new reqModule.Req(domain, false, false, 'hd');
this.req = new reqModule.Req(domain, debug, false, 'hd');
}
public async doInit() {
@ -494,6 +494,7 @@ export default class Hidive implements ServiceClass {
let dlFailed = false;
//let dlVideoOnce = false; // Variable to save if best selected video quality was downloaded
let subsMargin = 0;
let videoIndex = 0;
const chosenFontSize = options.originalFontSize ? fontSize : options.fontSize;
for (const videoData of videoUrls) {
if(videoData.seriesTitle && videoData.episodeNumber && videoData.episodeTitle){
@ -616,7 +617,7 @@ export default class Hidive implements ServiceClass {
console.info(`Selected quality: ${Object.keys(plSelectedList).find(a => plSelectedList[a] === selPlUrl)} @ ${plSelectedServer}`);
console.info('Stream URL:', selPlUrl);
// TODO check filename
const outFile = parseFileName(options.fileName + '.' + lang.name, variables, options.numbers, options.override).join(path.sep);
const outFile = parseFileName(options.fileName + '.' + lang.name + '.' + videoIndex, variables, options.numbers, options.override).join(path.sep);
fileName = parseFileName(options.fileName, variables, options.numbers, options.override).join(path.sep);
console.info(`Output filename: ${outFile}`);
const chunkPage = await this.req.getData(selPlUrl);
@ -675,12 +676,19 @@ export default class Hidive implements ServiceClass {
fileName = parseFileName(options.fileName, variables, options.numbers, options.override).join(path.sep);
console.info('Downloading skipped!');
}
videoIndex++;
await this.sleep(options.waittime);
}
if(options.dlsubs.indexOf('all') > -1){
options.dlsubs = ['all'];
}
if (options.nosubs) {
console.info('Subtitles downloading disabled from nosubs flag.');
options.skipsubs = true;
}
if(!options.skipsubs && options.dlsubs.indexOf('none') == -1) {
if(subUrls.length > 0) {
let subIndex = 0;
@ -739,6 +747,7 @@ export default class Hidive implements ServiceClass {
onlyVid: [],
skipSubMux: options.skipSubMux,
inverseTrackOrder: true,
keepAllVideos: options.keepAllVideos,
onlyAudio: [],
output: `${options.output}.${options.mp4 ? 'mp4' : 'mkv'}`,
subtitles: data.filter(a => a.type === 'Subtitle').map((a) : SubtitleInput => {
@ -792,4 +801,10 @@ export default class Hidive implements ServiceClass {
if (isMuxed && !options.nocleanup)
merger.cleanUp();
}
public sleep(ms: number) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
}

View file

@ -55,11 +55,11 @@ import update from './modules/module.updater';
if (key.endsWith('crunchy.js') || key.endsWith('funi.js') || key.endsWith('hidive.js'))
delete require.cache[key];
});
const service = new (argv.service === 'funi' ? (await import('./funi')).default : argv.service === 'hidive' ? (await import('./hidive')).default : (await import('./crunchy')).default)() as ServiceClass;
const service = new (argv.service === 'funi' ? (await import('./funi')).default : argv.service === 'hidive' ? (await import('./hidive')).default : (await import('./crunchy')).default)(argv.debug) as ServiceClass;
await service.cli();
}
} else {
const service = new (argv.service === 'funi' ? (await import('./funi')).default : argv.service === 'hidive' ? (await import('./hidive')).default : (await import('./crunchy')).default)() as ServiceClass;
const service = new (argv.service === 'funi' ? (await import('./funi')).default : argv.service === 'hidive' ? (await import('./hidive')).default : (await import('./crunchy')).default)(argv.debug) as ServiceClass;
await service.cli();
}
})();

View file

@ -33,6 +33,7 @@ let argvC: {
series: string | undefined;
s: string | undefined;
e: string | undefined;
extid: string | undefined;
q: number;
x: number;
kstream: number;
@ -47,6 +48,7 @@ let argvC: {
fontSize: number;
allDubs: boolean;
timeout: number;
waittime: number;
simul: boolean;
mp4: boolean;
skipmux: boolean | undefined;
@ -64,6 +66,7 @@ let argvC: {
dlVideoOnce: boolean;
removeBumpers: boolean;
originalFontSize: boolean;
keepAllVideos: boolean;
};
export type ArgvType = typeof argvC;

View file

@ -165,6 +165,17 @@ const args: TAppArg<boolean|number|string|unknown[]>[] = [
usage: '${selection}',
alias: 'episode'
},
{
name: 'extid',
group: 'dl',
describe: 'Set the external id to lookup/download',
docDescribe: 'Set the external id to lookup/download.'
+ '\nAllows you to download or view legacy Crunchyroll Ids ',
service: ['crunchy'],
type: 'string',
usage: '${selection}',
alias: 'externalid'
},
{
name: 'q',
group: 'dl',
@ -360,6 +371,18 @@ const args: TAppArg<boolean|number|string|unknown[]>[] = [
default: 15 * 1000
}
},
{
name: 'waittime',
group: 'dl',
type: 'number',
describe: 'Set the time the program waits between downloads. Set in millisecods',
docDescribe: true,
service: ['crunchy','hidive'],
usage: '${waittime}',
default: {
default: 0 * 1000
}
},
{
name: 'simul',
group: 'dl',
@ -384,6 +407,18 @@ const args: TAppArg<boolean|number|string|unknown[]>[] = [
default: false
}
},
{
name: 'keepAllVideos',
group: 'mux',
describe: 'Keeps all videos when merging instead of discarding extras',
docDescribe: 'If set to true, it will keep all videos in the merge process, rather than discarding the extra videos.',
service: ['crunchy','hidive'],
type: 'boolean',
usage: '',
default: {
default: false
}
},
{
name: 'skipmux',
describe: 'Skip muxing video, audio and subtitles',

View file

@ -30,8 +30,15 @@ const languages: LanguageItem[] = [
{ cr_locale: 'hi-IN', locale: 'hi', code: 'hin', name: 'Hindi' },
{ funi_locale: 'zhMN', locale: 'zh', code: 'cmn', name: 'Chinese (Mandarin, PRC)' },
{ cr_locale: 'zh-CN', locale: 'zh-CN', code: 'zho', name: 'Chinese (Mainland China)' },
{ cr_locale: 'zh-TW', locale: 'zh-TW', code: 'chi', name: 'Chinese (Taiwan)' },
{ cr_locale: 'ko-KR', hd_locale: 'Korean', locale: 'ko', code: 'kor', name: 'Korean' },
{ cr_locale: 'ja-JP', hd_locale: 'Japanese', funi_locale: 'jaJP', locale: 'ja', code: 'jpn', name: 'Japanese' },
{ cr_locale: 'ca-ES', locale: 'ca-ES', code: 'cat', name: 'Catalan' },
{ cr_locale: 'pl-PL', locale: 'pl-PL', code: 'pol', name: 'Polish' },
{ cr_locale: 'th-TH', locale: 'th-TH', code: 'tha', name: 'Thai' },
{ cr_locale: 'ta-IN', locale: 'ta-IN', code: 'tam', name: 'Tamil (India)' },
{ cr_locale: 'ms-MY', locale: 'ms-MY', code: 'may', name: 'Malay (Malaysia)' },
{ locale: 'un', code: 'und', name: 'Unknown' },
];
// add en language names

View file

@ -36,6 +36,7 @@ export type MergerOptions = {
videoTitle?: string,
simul?: boolean,
inverseTrackOrder?: boolean,
keepAllVideos?: boolean,
fonts?: ParsedFont[],
skipSubMux?: boolean,
options: {
@ -163,7 +164,7 @@ class Merger {
for (const vid of this.options.videoAndAudio) {
const audioTrackNum = this.options.inverseTrackOrder ? '0' : '1';
const videoTrackNum = this.options.inverseTrackOrder ? '1' : '0';
if (!hasVideo) {
if (!hasVideo || this.options.keepAllVideos) {
args.push(
`--video-tracks ${videoTrackNum}`,
`--audio-tracks ${audioTrackNum}`
@ -196,17 +197,16 @@ class Merger {
for (const aud of this.options.onlyAudio) {
const trackName = aud.lang.name;
const trackNum = this.options.inverseTrackOrder ? '0' : '1';
args.push('--track-name', `${trackNum}:"${trackName}"`);
args.push(`--language ${trackNum}:${aud.lang.code}`);
args.push('--track-name', `0:"${trackName}"`);
args.push(`--language 0:${aud.lang.code}`);
args.push(
'--no-video',
`--audio-tracks ${trackNum}`
'--audio-tracks 0'
);
if (this.options.defaults.audio.code === aud.lang.code) {
args.push(`--default-track ${trackNum}`);
args.push('--default-track 0');
} else {
args.push(`--default-track ${trackNum}:0`);
args.push('--default-track 0:0');
}
args.push(`"${aud.path}"`);
}

View file

@ -54,6 +54,9 @@ const parseSelect = (selectString: string, but = false) : {
if (part.match(/[0-9A-Z]{9}/)) {
select.push(part);
return;
} else if (part.match(/[A-Z]{3}\.[0-9]*/)) {
select.push(part);
return;
}
const match = part.match(/[A-Za-z]+/);
if (match && match.length > 0) {

View file

@ -1,7 +1,7 @@
{
"name": "multi-downloader-nx",
"short_name": "aniDL",
"version": "4.1.0",
"version": "4.2.0",
"description": "Download videos from Funimation, Crunchyroll, or Hidive via cli",
"keywords": [
"download",