mirror of
https://github.com/anidl/multi-downloader-nx.git
synced 2026-05-11 12:30:35 +00:00
Merge pull request #499 from anidl/4.2.0
4.2.0 - Bunch of fixes and additions
This commit is contained in:
commit
88d5c11032
12 changed files with 194 additions and 40 deletions
3
@types/crunchyTypes.d.ts
vendored
3
@types/crunchyTypes.d.ts
vendored
|
|
@ -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',
|
||||
|
|
|
|||
104
crunchy.ts
104
crunchy.ts
|
|
@ -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 != ''){
|
||||
|
|
|
|||
|
|
@ -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**
|
||||
|
|
|
|||
|
|
@ -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}`);
|
||||
});
|
||||
|
|
|
|||
19
hidive.ts
19
hidive.ts
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
4
index.ts
4
index.ts
|
|
@ -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();
|
||||
}
|
||||
})();
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}"`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Reference in a new issue