Crunchy multi dubs

This commit is contained in:
Izuco 2021-11-19 23:53:09 +01:00
parent 768f833644
commit 846baea609
No known key found for this signature in database
GPG key ID: 318460063D70949F
5 changed files with 741 additions and 84 deletions

View file

@ -1,3 +1,5 @@
import { LanguageItem } from "../modules/module.langsData"
export type CrunchyEpMeta = {
mediaId: string,
seasonTitle: string,
@ -7,6 +9,18 @@ export type CrunchyEpMeta = {
seasonID: string
}
export type CrunchyEpMetaMultiDub = {
data: {
mediaId: string,
lang: LanguageItem,
playback?: string
}[],
seasonTitle: string,
episodeNumber: string,
episodeTitle: string,
seasonID: string
}
export type ParseItem = {
__class__?: string;
isSelected?: boolean,
@ -25,4 +39,51 @@ export type ParseItem = {
last_public?: string,
subtitle_locales?: string[],
availability_notes?: string
}
}
export interface SeriesSearch {
__class__: string;
__href__: string;
__resource_key__: string;
__links__: Actions;
__actions__: Actions;
total: number;
items: SeriesSearchItem[];
}
export interface SeriesSearchItem {
__class__: string;
__href__: string;
__resource_key__: string;
__links__: Links;
__actions__: string[];
id: string;
channel_id: string;
title: string;
slug_title: string;
series_id: string;
season_number: number;
is_complete: boolean;
description: string;
keywords: any[];
season_tags: string[];
images: Actions;
is_mature: boolean;
mature_blocked: boolean;
is_subbed: boolean;
is_dubbed: boolean;
is_simulcast: boolean;
seo_title: string;
seo_description: string;
availability_notes: string;
}
export interface Links {
"season/channel": Season;
"season/episodes": Season;
"season/series": Season;
}
export interface Season {
href: string;
}

View file

@ -15,7 +15,7 @@ import * as langsData from './modules/module.langsData';
import * as yamlCfg from './modules/module.cfg-loader';
import * as yargs from './modules/module.app-args';
import * as epsFilter from './modules/module.eps-filter';
import Merger, { Font } from './modules/module.merger';
import Merger, { Font, MergerOptions } from './modules/module.merger';
// new-cfg paths
const cfg = yamlCfg.loadCfg();
@ -50,8 +50,8 @@ const appstore: {
import { domain, api } from './modules/module.api-urls';
import * as reqModule from './modules/module.req';
import { CrunchySearch } from './@types/crunchySearch';
import { CrunchyEpisodeList } from './@types/crunchyEpisodeList';
import { CrunchyEpMeta, ParseItem } from './@types/crunchyTypes';
import { CrunchyEpisodeList, Item } from './@types/crunchyEpisodeList';
import { CrunchyEpMeta, CrunchyEpMetaMultiDub, ParseItem, SeriesSearch, SeriesSearchItem } from './@types/crunchyTypes';
import { ObjectInfo } from './@types/objectInfo';
import parseFileName, { Variable } from './modules/module.filename';
import { PlaybackData } from './@types/playbackData';
@ -84,6 +84,7 @@ export default (async () => {
else if(argv.series && argv.series.match(/^[0-9A-Z]{9}$/)){
await refreshToken();
await getSeriesById();
await downloadFromSeriesID();
}
else if(argv['movie-listing'] && argv['movie-listing'].match(/^[0-9A-Z]{9}$/)){
await refreshToken();
@ -91,6 +92,10 @@ export default (async () => {
}
else if(argv.s && argv.s.match(/^[0-9A-Z]{9}$/)){
await refreshToken();
if (argv.dubLang.length > 1) {
console.log('[INFO] One show can only be downloaded with one dub. Use --srz instead.')
argv.dubLang = argv.dubLang[0];
}
await getSeasonById();
}
else if(argv.e){
@ -524,7 +529,7 @@ async function getSeriesById(pad?: number, hideSeriesTitle?: boolean){
return;
}
// parse data
const seasonsList = JSON.parse(seriesSeasonListReq.res.body);
const seasonsList = JSON.parse(seriesSeasonListReq.res.body) as SeriesSearch;
if(seasonsList.total < 1){
console.log('[INFO] Series is empty!');
return;
@ -1137,7 +1142,24 @@ async function getMedia(mMeta: CrunchyEpMeta){
// go to muxing
if(!argv.skipmux && !dlFailed){
await muxStreams();
await muxStreams({
onlyAudio: [],
onlyVid: [],
videoAndAudio: [{
path: (path.isAbsolute(appstore.out as string) ? appstore.out as string : path.join(cfg.dir.content, appstore.out as string)) + '.ts',
lang: appstore.lang as string,
lookup: false
}],
subtitels: appstore.sxList.map(a => ({
language: a.language.code,
file: a.path,
lookup: false,
title: a.title
})),
output: (path.isAbsolute(appstore.out as string) ? appstore.out as string : path.join(cfg.dir.content, appstore.out as string)) + '.' + (argv.mp4 ? 'mp4' : 'mkv'),
simul: false,
fonts: Merger.makeFontsList(cfg.dir.fonts, appstore.sxList)
});
}
else{
console.log();
@ -1145,25 +1167,8 @@ async function getMedia(mMeta: CrunchyEpMeta){
}
async function muxStreams(){
const merger = new Merger({
onlyAudio: [],
onlyVid: [],
videoAndAudio: [{
path: (path.isAbsolute(appstore.out as string) ? appstore.out as string : path.join(cfg.dir.content, appstore.out as string)) + '.ts',
lang: appstore.lang as string,
lookup: false
}],
subtitels: appstore.sxList.map(a => ({
language: a.language.code,
file: a.path,
lookup: false,
title: a.title
})),
output: (path.isAbsolute(appstore.out as string) ? appstore.out as string : path.join(cfg.dir.content, appstore.out as string)) + '.' + (argv.mp4 ? 'mp4' : 'mkv'),
simul: false,
fonts: Merger.makeFontsList(cfg.dir.fonts, appstore.sxList)
});
async function muxStreams(options: MergerOptions){
const merger = new Merger(options);
const bin = Merger.checkMerger(cfg.bin, argv.mp4);
// collect fonts info
// mergers
@ -1182,4 +1187,595 @@ async function muxStreams(){
}
if (isMuxed)
merger.cleanUp();
}
// MULTI DOWNLOADING
const downloadFromSeriesID = async () => {
const parsed = await parseSeriesById();
if (!parsed)
return;
const result = parseSeriesResult(parsed);
/*if(selectedMedia.length > 1){
appstore.isBatch = true;
}*/
const episodes : Record<string, {
items: Item[],
langs: langsData.LanguageItem[]
}> = {}
for (let key of Object.keys(result)) {
const s = result[key];
(await getSeasonDataById(s))?.items.forEach(a => {
if (Object.prototype.hasOwnProperty.call(episodes, a.episode_number?.toString() as string)) {
let item = episodes[a.episode_number?.toString() as string];
item.items.push(a);
item.langs.push(langsData.languages.find(a => a.code == key) as langsData.LanguageItem);
} else {
episodes[a.episode_number?.toString() as string] = {
items: [a],
langs: [langsData.languages.find(a => a.code == key) as langsData.LanguageItem]
};
}
})
}
for (let key of Object.keys(episodes)) {
const item = episodes[key];
//if (item.items[0].episode_number == null)
// continue;
console.log(`[${item.items[0].episode}] ${
item.items.find(a => !a.season_title.includes('('))?.season_title as string
} - ${item.items[0].title} [${
item.items.map((a, index) => {
return `${a.is_premium_only ? '☆ ' : ''}${item.langs[index].name}`
}).join(', ')
}]`)
}
console.log()
console.log('-'.repeat(30));
console.log()
const selected = itemSelectMultiDub(episodes)
for (let key of Object.keys(selected)) {
const item = selected[key];
console.log(`[${item.episodeNumber}] - ${item.episodeTitle} [${
item.data.map(a => {
return `${a.lang.name}`
}).join(', ')
}]`)
}
for (let key of Object.keys(selected)) {
const item = selected[key];
let res = await getMediaList(item);
if (!res)
return;
muxStreams({
onlyAudio: [],
onlyVid: [],
output: (path.isAbsolute(appstore.out as string) ? appstore.out as string : path.join(cfg.dir.content, appstore.out as string)) + '.' + (argv.mp4 ? 'mp4' : 'mkv'),
subtitels: appstore.sxList.map(a => ({
language: a.language.code,
file: a.path,
lookup: false,
title: a.title
})),
videoAndAudio: res,
simul: false,
fonts: Merger.makeFontsList(cfg.dir.fonts, appstore.sxList)
})
}
}
const itemSelectMultiDub = (eps: Record<string, {
items: Item[],
langs: langsData.LanguageItem[]
}>) => {
const doEpsFilter = new epsFilter.doFilter();
const selEps = doEpsFilter.checkFilter(argv.e);
const ret: Record<string, CrunchyEpMetaMultiDub> = {};
const epNumList: {
sp: number
} = { sp: 0 };
const epNumLen = epsFilter.epNumLen;
appstore.sxList = [];
for (let key of Object.keys(eps)) {
const itemE = eps[key];
itemE.items.forEach((item, index) => {
if (!argv.dubLang.includes(itemE.langs[index].code))
return;
item.hide_season_title = true;
if(item.season_title == '' && item.series_title != ''){
item.season_title = item.series_title;
item.hide_season_title = false;
item.hide_season_number = true;
}
if(item.season_title == '' && item.series_title == ''){
item.season_title = 'NO_TITLE';
}
// set data
const epMeta: CrunchyEpMeta = {
mediaId: item.id,
seasonTitle: itemE.items.find(a => !a.season_title.includes('('))?.season_title as string,
episodeNumber: item.episode,
episodeTitle: item.title,
seasonID: item.season_id
};
if(item.playback){
epMeta.playback = item.playback;
}
// find episode numbers
const epNum = item.episode;
let isSpecial = false;
if(!epNum.match(/^\d+$/)){
isSpecial = true;
epNumList.sp++;
}
const selEpId = (
isSpecial
? 'S' + epNumList.sp.toString().padStart(epNumLen['S'], '0')
: '' + parseInt(epNum, 10).toString().padStart(epNumLen['E'], '0')
);
if(selEps.indexOf(selEpId) > -1 && item.playback){
if (Object.prototype.hasOwnProperty.call(ret, key)) {
const epMe = ret[key];
epMe.data.push({
lang: itemE.langs[index],
mediaId: epMeta.mediaId,
playback: epMeta.playback
})
} else {
ret[key] = {
data: [
{
lang: itemE.langs[index],
mediaId: epMeta.mediaId,
playback: epMeta.playback
}
],
...epMeta
}
}
}
// show ep
item.seq_id = selEpId;
})
}
return ret;
}
const parseSeriesResult = (seasonsList: SeriesSearch) : Record<string, SeriesSearchItem> => {
const ret: Record<string, SeriesSearchItem> = {};
for (let item of seasonsList.items) {
for (let lang of langsData.languages) {
if (item.title.includes(`(${lang.name} Dub)`)) {
ret[lang.code] = item;
} else if (item.is_subbed && !item.is_dubbed && lang.code == 'jpn') {
ret[lang.code] = item;
}
}
}
return ret;
}
async function parseSeriesById() {
if(!cmsToken.cms){
console.log('[ERROR] Authentication required!');
return;
}
const seriesSeasonListReqOpts = [
api.beta_cms,
cmsToken.cms.bucket,
'/seasons?',
new URLSearchParams({
'series_id': argv.series as string,
'Policy': cmsToken.cms.policy,
'Signature': cmsToken.cms.signature,
'Key-Pair-Id': cmsToken.cms.key_pair_id,
}),
].join('');
// seasons list
const seriesSeasonListReq = await req.getData(seriesSeasonListReqOpts);
if(!seriesSeasonListReq.ok || !seriesSeasonListReq.res){
console.log('[ERROR] Series Request FAILED!');
return;
}
// parse data
const seasonsList = JSON.parse(seriesSeasonListReq.res.body) as SeriesSearch;
if(seasonsList.total < 1){
console.log('[INFO] Series is empty!');
return;
}
return seasonsList;
}
async function getSeasonDataById(item: SeriesSearchItem, log = false){
if(!cmsToken.cms){
console.log('[ERROR] Authentication required!');
return;
}
const showInfoReqOpts = [
api.beta_cms,
cmsToken.cms.bucket,
'/seasons/',
item.id,
'?',
new URLSearchParams({
'Policy': cmsToken.cms.policy,
'Signature': cmsToken.cms.signature,
'Key-Pair-Id': cmsToken.cms.key_pair_id,
}),
].join('');
const showInfoReq = await req.getData(showInfoReqOpts);
if(!showInfoReq.ok || !showInfoReq.res){
console.log('[ERROR] Show Request FAILED!');
return;
}
const showInfo = JSON.parse(showInfoReq.res.body);
if (log)
parseObject(showInfo, 0);
const reqEpsListOpts = [
api.beta_cms,
cmsToken.cms.bucket,
'/episodes?',
new URLSearchParams({
'season_id': item.id as string,
'Policy': cmsToken.cms.policy,
'Signature': cmsToken.cms.signature,
'Key-Pair-Id': cmsToken.cms.key_pair_id,
}),
].join('');
const reqEpsList = await req.getData(reqEpsListOpts);
if(!reqEpsList.ok || !reqEpsList.res){
console.log('[ERROR] Episode List Request FAILED!');
return;
}
const episodeList = JSON.parse(reqEpsList.res.body) as CrunchyEpisodeList;
if(episodeList.total < 1){
console.log(' [INFO] Season is empty!');
return;
}
return episodeList;
}
async function getMediaList(medias: CrunchyEpMetaMultiDub){
let mediaName = '...';
if(medias.seasonTitle && medias.episodeNumber && medias.episodeTitle){
mediaName = `${medias.seasonTitle} - ${medias.episodeNumber} - ${medias.episodeTitle}`;
}
let files: {
path: string,
lang: string
}[] = [];
if(medias.data.every(a => !a.playback)){
console.log('[WARN] Video not available!');
return;
}
for (const mMeta of medias.data) {
console.log(`[INFO] Requesting: [${mMeta.mediaId}] ${mediaName}`);
const playbackReq = await req.getData(mMeta.playback as string);
if(!playbackReq.ok || !playbackReq.res){
console.log('[ERROR] Request Stream URLs FAILED!');
return;
}
const pbData = JSON.parse(playbackReq.res.body) as PlaybackData;
appstore.fn = ([
['title', medias.episodeTitle],
['episode', medias.episodeNumber],
['service', 'CR'],
['showTitle', medias.seasonTitle],
['season', medias.seasonID]
] as [yargs.AvailableFilenameVars, string|number][]).map((a): Variable => {
return {
name: a[0],
replaceWith: a[1],
type: typeof a[1]
} as Variable;
});
let streams = [];
let hsLangs: string[] = [];
const pbStreams = pbData.streams;
for(const s of Object.keys(pbStreams)){
if(s.match(/hls/) && !s.match(/drm/) && !s.match(/trailer/)){
const pb = Object.values(pbStreams[s]).map(v => {
v.hardsub_lang = v.hardsub_locale
? langsData.fixAndFindCrLC(v.hardsub_locale).locale
: v.hardsub_locale;
if(v.hardsub_lang && hsLangs.indexOf(v.hardsub_lang) < 0){
hsLangs.push(v.hardsub_lang);
}
return {
...v,
...{ format: s }
};
});
streams.push(...pb);
}
}
if(streams.length < 1){
console.log('[WARN] No full streams found!');
return;
}
const audDub = langsData.findLang(langsData.fixLanguageTag(pbData.audio_locale)).code;
hsLangs = langsData.sortTags(hsLangs);
streams = streams.map((s) => {
s.audio_lang = audDub;
s.hardsub_lang = s.hardsub_lang ? s.hardsub_lang : '-';
s.type = `${s.format}/${s.audio_lang}/${s.hardsub_lang}`;
return s;
});
let dlFailed = false;
if(argv.hslang != 'none'){
if(hsLangs.indexOf(argv.hslang) > -1){
console.log('[INFO] Selecting stream with %s hardsubs', langsData.locale2language(argv.hslang).language);
streams = streams.filter((s) => {
if(s.hardsub_lang == '-'){
return false;
}
return s.hardsub_lang == argv.hslang ? true : false;
});
}
else{
console.log('[WARN] Selected stream with %s hardsubs not available', langsData.locale2language(argv.hslang).language);
if(hsLangs.length > 0){
console.log('[WARN] Try other hardsubs stream:', hsLangs.join(', '));
}
dlFailed = true;
}
}
else{
streams = streams.filter((s) => {
if(s.hardsub_lang != '-'){
return false;
}
return true;
});
if(streams.length < 1){
console.log('[WARN] Raw streams not available!');
if(hsLangs.length > 0){
console.log('[WARN] Try hardsubs stream:', hsLangs.join(', '));
}
dlFailed = true;
}
console.log('[INFO] Selecting raw stream');
}
let curStream;
if(!dlFailed){
argv.kstream = typeof argv.kstream == 'number' ? argv.kstream : 1;
argv.kstream = argv.kstream > streams.length ? 1 : argv.kstream;
streams.forEach((s, i) => {
const isSelected = argv.kstream == i + 1 ? '✓' : ' ';
console.log('[INFO] Full stream found! (%s%s: %s )', isSelected, i + 1, s.type);
});
console.log('[INFO] Downloading video...');
curStream = streams[argv.kstream-1];
if(argv.dubLang != curStream.audio_lang){
argv.dubLang = curStream.audio_lang as string;
console.log(`[INFO] audio language code detected, setted to ${curStream.audio_lang} for this episode`);
}
console.log('[INFO] Playlists URL: %s (%s)', curStream.url, curStream.type);
}
if(!argv.skipdl && !dlFailed && curStream){
const streamPlaylistsReq = await req.getData(curStream.url);
if(!streamPlaylistsReq.ok || !streamPlaylistsReq.res){
console.log('[ERROR] CAN\'T FETCH VIDEO PLAYLISTS!');
dlFailed = true;
}
else{
const streamPlaylists = m3u8(streamPlaylistsReq.res.body);
const plServerList: string[] = [],
plStreams: Record<string, Record<string, string>> = {},
plQuality: {
str: string,
dim: string,
RESOLUTION: {
width: number,
height: number
}
}[] = [];
for(const pl of streamPlaylists.playlists){
// set quality
const plResolution = pl.attributes.RESOLUTION;
const plResolutionText = `${plResolution.width}x${plResolution.height}`;
// parse uri
const plUri = new URL(pl.uri);
let plServer = plUri.hostname;
// set server list
if(plUri.searchParams.get('cdn')){
plServer += ` (${plUri.searchParams.get('cdn')})`;
}
if(!plServerList.includes(plServer)){
plServerList.push(plServer);
}
// add to server
if(!Object.keys(plStreams).includes(plServer)){
plStreams[plServer] = {};
}
if(
plStreams[plServer][plResolutionText]
&& plStreams[plServer][plResolutionText] != pl.uri
&& typeof plStreams[plServer][plResolutionText] != 'undefined'
){
console.log(`[WARN] Non duplicate url for ${plServer} detected, please report to developer!`);
}
else{
plStreams[plServer][plResolutionText] = pl.uri;
}
// set plQualityStr
const plBandwidth = Math.round(pl.attributes.BANDWIDTH/1024);
const qualityStrAdd = `${plResolutionText} (${plBandwidth}KiB/s)`;
const qualityStrRegx = new RegExp(qualityStrAdd.replace(/(:|\(|\)|\/)/g, '\\$1'), 'm');
const qualityStrMatch = !plQuality.map(a => a.str).join('\r\n').match(qualityStrRegx);
if(qualityStrMatch){
plQuality.push({
str: qualityStrAdd,
dim: plResolutionText,
RESOLUTION: plResolution
});
}
}
argv.server = argv.x > plServerList.length ? 1 : argv.x;
const plSelectedServer = plServerList[argv.x - 1];
const plSelectedList = plStreams[plSelectedServer];
plQuality.sort((a, b) => {
const aMatch = a.dim.match(/[0-9]+/) || [];
const bMatch = b.dim.match(/[0-9]+/) || [];
return parseInt(aMatch[0]) - parseInt(bMatch[0]);
});
let quality = argv.q;
if (quality > plQuality.length) {
console.log(`[WARN] The requested quality of ${argv.q} is greater than the maximun ${plQuality.length}.\n[WARN] Therefor the maximum will be capped at ${plQuality.length}.`);
quality = plQuality.length;
}
const selPlUrl = quality === 0 ? plSelectedList[plQuality[plQuality.length - 1].dim as string] :
plSelectedList[plQuality.map(a => a.dim)[quality - 1]] ? plSelectedList[plQuality.map(a => a.dim)[quality - 1]] : '';
console.log(`[INFO] Servers available:\n\t${plServerList.join('\n\t')}`);
console.log(`[INFO] Available qualities:\n\t${plQuality.map((a, ind) => `[${ind+1}] ${a.str}`).join('\n\t')}`);
if(selPlUrl != ''){
appstore.fn.push({
name: 'height',
type: 'number',
replaceWith: quality === 0 ? plQuality[plQuality.length - 1].RESOLUTION.height as number : plQuality[quality - 1].RESOLUTION.height
}, {
name: 'width',
type: 'number',
replaceWith: quality === 0 ? plQuality[plQuality.length - 1].RESOLUTION.width as number : plQuality[quality - 1].RESOLUTION.width
});
const lang = curStream.audio_lang;
console.log(`[INFO] Selected quality: ${Object.keys(plSelectedList).find(a => plSelectedList[a] === selPlUrl)} @ ${plSelectedServer}`);
if(argv['show-stream-url']){
console.log('[INFO] Stream URL:', selPlUrl);
}
// TODO check filename
appstore.out = parseFileName(argv.fileName, appstore.fn, argv.numbers).join(path.sep);
const outFile = parseFileName(argv.fileName + '.' + mMeta.lang.name, appstore.fn, argv.numbers).join(path.sep);
console.log(`[INFO] Output filename: ${outFile}`);
const chunkPage = await req.getData(selPlUrl);
if(!chunkPage.ok || !chunkPage.res){
console.log('[ERROR] CAN\'T FETCH VIDEO PLAYLIST!');
dlFailed = true;
}
else{
const chunkPlaylist = m3u8(chunkPage.res.body);
const totalParts = chunkPlaylist.segments.length;
const mathParts = Math.ceil(totalParts / argv.partsize);
const mathMsg = `(${mathParts}*${argv.partsize})`;
console.log('[INFO] Total parts in stream:', totalParts, mathMsg);
const tsFile = path.isAbsolute(outFile as string) ? outFile : path.join(cfg.dir.content, outFile);
const split = outFile.split(path.sep).slice(0, -1);
split.forEach((val, ind, arr) => {
const isAbsolut = path.isAbsolute(outFile as string);
if (!fs.existsSync(path.join(isAbsolut ? '' : cfg.dir.content, ...arr.slice(0, ind), val)))
fs.mkdirSync(path.join(isAbsolut ? '' : cfg.dir.content, ...arr.slice(0, ind), val));
});
const dlStreamByPl = await new streamdl({
output: `${tsFile}.ts`,
timeout: argv.timeout,
m3u8json: chunkPlaylist,
// baseurl: chunkPlaylist.baseUrl,
threads: argv.partsize
}).download();
if(!dlStreamByPl.ok){
console.log(`[ERROR] DL Stats: ${JSON.stringify(dlStreamByPl.parts)}\n`);
dlFailed = true;
}
files.push({
path: `${tsFile}.ts`,
lang: lang as string
})
}
}
else{
console.log('[ERROR] Quality not selected!\n');
dlFailed = true;
}
}
}
else if(argv.skipdl){
console.log('[INFO] Downloading skipped!');
}
if(argv.dlsubs.indexOf('all') > -1){
argv.dlsubs = ['all'];
}
if(argv.hslang != 'none'){
console.log('[WARN] Subtitles downloading disabled for hardsubs streams.');
argv.skipsubs = true;
}
if(!argv.skipsubs && argv.dlsubs.indexOf('none') == -1){
if(pbData.subtitles && Object.values(pbData.subtitles).length > 0){
const subsData = Object.values(pbData.subtitles);
const subsDataMapped = subsData.map((s) => {
const subLang = langsData.fixAndFindCrLC(s.locale);
return {
...s,
locale: subLang,
language: subLang.locale,
titile: subLang.language
};
});
const subsArr = langsData.sortSubtitles<typeof subsDataMapped[0]>(subsDataMapped, 'language');
for(const subsIndex in subsArr){
const subsItem = subsArr[subsIndex];
const langItem = subsItem.locale;
const sxData: Partial<sxItem> = {};
sxData.language = langItem;
sxData.file = langsData.subsFile(appstore.out as string, subsIndex, langItem);
sxData.path = path.join(cfg.dir.content, sxData.file);
if (appstore.sxList.some(a => a.language.code == langItem.code))
continue;
if(argv.dlsubs.includes('all') || argv.dlsubs.includes(langItem.locale)){
const subsAssReq = await req.getData(subsItem.url);
if(subsAssReq.ok && subsAssReq.res){
const sBody = '\ufeff' + subsAssReq.res.body;
sxData.title = sBody.split('\r\n')[1].replace(/^Title: /, '');
sxData.title = `${langItem.language} / ${sxData.title}`;
sxData.fonts = fontsData.assFonts(sBody) as Font[];
fs.writeFileSync(path.join(cfg.dir.content, sxData.file), sBody);
console.log(`[INFO] Subtitle downloaded: ${sxData.file}`);
appstore.sxList.push(sxData as sxItem);
}
else{
console.log(`[WARN] Failed to download subtitle: ${sxData.file}`);
}
}
}
}
else{
console.log('[WARN] Can\'t find urls for subtitles!');
}
}
else{
console.log('[INFO] Subtitles downloading skipped!');
}
}
return files;
}

View file

@ -195,7 +195,7 @@ const appArgv = (cfg: {
describe: 'Set the language to download (Crunchy only)',
choices: langsData.dubLanguageCodes,
default: parseDefault('dubLanguage', langsData.dubLanguageCodes.slice(-1)[0]),
type: 'string',
array: true,
})
.option('all', {
group: groups.dl,

102
package-lock.json generated
View file

@ -1,6 +1,6 @@
{
"name": "multi-downloader-nx",
"version": "1.0.3",
"version": "1.1.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -278,9 +278,9 @@
}
},
"@types/node": {
"version": "16.11.7",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.7.tgz",
"integrity": "sha512-QB5D2sqfSjCmTuWcBWyJ+/44bcjO7VbjSbOE0ucoVbAsSNQc4Lt6QkgkVXkTDwkL4z/beecZNDvVX15D4P8Jbw=="
"version": "16.11.9",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.9.tgz",
"integrity": "sha512-MKmdASMf3LtPzwLyRrFjtFFZ48cMf8jmX5VRYrDQiJa8Ybu5VAmkqBWqKU8fdCwD8ysw4mQ9nrEHvzg6gunR7A=="
},
"@types/responselike": {
"version": "1.0.0",
@ -291,9 +291,9 @@
}
},
"@types/yargs": {
"version": "17.0.5",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.5.tgz",
"integrity": "sha512-4HNq144yhaVjJs+ON6A07NEoi9Hh0Rhl/jI9Nt/l/YRjt+T6St/QK3meFARWZ8IgkzoD1LC0PdTdJenlQQi2WQ==",
"version": "17.0.7",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.7.tgz",
"integrity": "sha512-OvLKmpKdea1aWtqHv9bxVVcMoT6syAeK+198dfETIFkAevYRGwqh4H+KFxfjUETZuUuE5sQCAFwdOdoHUdo8eg==",
"dev": true,
"requires": {
"@types/yargs-parser": "*"
@ -306,13 +306,13 @@
"dev": true
},
"@typescript-eslint/eslint-plugin": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.3.1.tgz",
"integrity": "sha512-cFImaoIr5Ojj358xI/SDhjog57OK2NqlpxwdcgyxDA3bJlZcJq5CPzUXtpD7CxI2Hm6ATU7w5fQnnkVnmwpHqw==",
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.4.0.tgz",
"integrity": "sha512-9/yPSBlwzsetCsGEn9j24D8vGQgJkOTr4oMLas/w886ZtzKIs1iyoqFrwsX2fqYEeUwsdBpC21gcjRGo57u0eg==",
"dev": true,
"requires": {
"@typescript-eslint/experimental-utils": "5.3.1",
"@typescript-eslint/scope-manager": "5.3.1",
"@typescript-eslint/experimental-utils": "5.4.0",
"@typescript-eslint/scope-manager": "5.4.0",
"debug": "^4.3.2",
"functional-red-black-tree": "^1.0.1",
"ignore": "^5.1.8",
@ -322,55 +322,55 @@
}
},
"@typescript-eslint/experimental-utils": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.3.1.tgz",
"integrity": "sha512-RgFn5asjZ5daUhbK5Sp0peq0SSMytqcrkNfU4pnDma2D8P3ElZ6JbYjY8IMSFfZAJ0f3x3tnO3vXHweYg0g59w==",
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.4.0.tgz",
"integrity": "sha512-Nz2JDIQUdmIGd6p33A+naQmwfkU5KVTLb/5lTk+tLVTDacZKoGQisj8UCxk7onJcrgjIvr8xWqkYI+DbI3TfXg==",
"dev": true,
"requires": {
"@types/json-schema": "^7.0.9",
"@typescript-eslint/scope-manager": "5.3.1",
"@typescript-eslint/types": "5.3.1",
"@typescript-eslint/typescript-estree": "5.3.1",
"@typescript-eslint/scope-manager": "5.4.0",
"@typescript-eslint/types": "5.4.0",
"@typescript-eslint/typescript-estree": "5.4.0",
"eslint-scope": "^5.1.1",
"eslint-utils": "^3.0.0"
}
},
"@typescript-eslint/parser": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.3.1.tgz",
"integrity": "sha512-TD+ONlx5c+Qhk21x9gsJAMRohWAUMavSOmJgv3JGy9dgPhuBd5Wok0lmMClZDyJNLLZK1JRKiATzCKZNUmoyfw==",
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.4.0.tgz",
"integrity": "sha512-JoB41EmxiYpaEsRwpZEYAJ9XQURPFer8hpkIW9GiaspVLX8oqbqNM8P4EP8HOZg96yaALiLEVWllA2E8vwsIKw==",
"dev": true,
"requires": {
"@typescript-eslint/scope-manager": "5.3.1",
"@typescript-eslint/types": "5.3.1",
"@typescript-eslint/typescript-estree": "5.3.1",
"@typescript-eslint/scope-manager": "5.4.0",
"@typescript-eslint/types": "5.4.0",
"@typescript-eslint/typescript-estree": "5.4.0",
"debug": "^4.3.2"
}
},
"@typescript-eslint/scope-manager": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.3.1.tgz",
"integrity": "sha512-XksFVBgAq0Y9H40BDbuPOTUIp7dn4u8oOuhcgGq7EoDP50eqcafkMVGrypyVGvDYHzjhdUCUwuwVUK4JhkMAMg==",
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.4.0.tgz",
"integrity": "sha512-pRxFjYwoi8R+n+sibjgF9iUiAELU9ihPBtHzocyW8v8D8G8KeQvXTsW7+CBYIyTYsmhtNk50QPGLE3vrvhM5KA==",
"dev": true,
"requires": {
"@typescript-eslint/types": "5.3.1",
"@typescript-eslint/visitor-keys": "5.3.1"
"@typescript-eslint/types": "5.4.0",
"@typescript-eslint/visitor-keys": "5.4.0"
}
},
"@typescript-eslint/types": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.3.1.tgz",
"integrity": "sha512-bG7HeBLolxKHtdHG54Uac750eXuQQPpdJfCYuw4ZI3bZ7+GgKClMWM8jExBtp7NSP4m8PmLRM8+lhzkYnSmSxQ==",
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.4.0.tgz",
"integrity": "sha512-GjXNpmn+n1LvnttarX+sPD6+S7giO+9LxDIGlRl4wK3a7qMWALOHYuVSZpPTfEIklYjaWuMtfKdeByx0AcaThA==",
"dev": true
},
"@typescript-eslint/typescript-estree": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.3.1.tgz",
"integrity": "sha512-PwFbh/PKDVo/Wct6N3w+E4rLZxUDgsoII/GrWM2A62ETOzJd4M6s0Mu7w4CWsZraTbaC5UQI+dLeyOIFF1PquQ==",
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.4.0.tgz",
"integrity": "sha512-nhlNoBdhKuwiLMx6GrybPT3SFILm5Gij2YBdPEPFlYNFAXUJWX6QRgvi/lwVoadaQEFsizohs6aFRMqsXI2ewA==",
"dev": true,
"requires": {
"@typescript-eslint/types": "5.3.1",
"@typescript-eslint/visitor-keys": "5.3.1",
"@typescript-eslint/types": "5.4.0",
"@typescript-eslint/visitor-keys": "5.4.0",
"debug": "^4.3.2",
"globby": "^11.0.4",
"is-glob": "^4.0.3",
@ -379,12 +379,12 @@
}
},
"@typescript-eslint/visitor-keys": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.3.1.tgz",
"integrity": "sha512-3cHUzUuVTuNHx0Gjjt5pEHa87+lzyqOiHXy/Gz+SJOCW1mpw9xQHIIEwnKn+Thph1mgWyZ90nboOcSuZr/jTTQ==",
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.4.0.tgz",
"integrity": "sha512-PVbax7MeE7tdLfW5SA0fs8NGVVr+buMPrcj+CWYWPXsZCH8qZ1THufDzbXm1xrZ2b2PA1iENJ0sRq5fuUtvsJg==",
"dev": true,
"requires": {
"@typescript-eslint/types": "5.3.1",
"@typescript-eslint/types": "5.4.0",
"eslint-visitor-keys": "^3.0.0"
}
},
@ -1584,16 +1584,16 @@
}
},
"got": {
"version": "11.8.2",
"resolved": "https://registry.npmjs.org/got/-/got-11.8.2.tgz",
"integrity": "sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ==",
"version": "11.8.3",
"resolved": "https://registry.npmjs.org/got/-/got-11.8.3.tgz",
"integrity": "sha512-7gtQ5KiPh1RtGS9/Jbv1ofDpBFuq42gyfEib+ejaRBJuj/3tQFeR5+gw57e4ipaU8c/rCjvX6fkQz2lyDlGAOg==",
"requires": {
"@sindresorhus/is": "^4.0.0",
"@szmarczak/http-timer": "^4.0.5",
"@types/cacheable-request": "^6.0.1",
"@types/responselike": "^1.0.0",
"cacheable-lookup": "^5.0.3",
"cacheable-request": "^7.0.1",
"cacheable-request": "^7.0.2",
"decompress-response": "^6.0.0",
"http2-wrapper": "^1.0.0-beta.5.2",
"lowercase-keys": "^2.0.0",
@ -1860,9 +1860,9 @@
}
},
"keyv": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.3.tgz",
"integrity": "sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.4.tgz",
"integrity": "sha512-vqNHbAc8BBsxk+7QBYLW0Y219rWcClspR6WSeoHYKG5mnsSoOH+BL1pWq02DDCVdvvuUny5rkBlzMRzoqc+GIg==",
"requires": {
"json-buffer": "3.0.1"
}
@ -3064,9 +3064,9 @@
"dev": true
},
"typescript": {
"version": "4.4.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz",
"integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==",
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.2.tgz",
"integrity": "sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==",
"dev": true
},
"universalify": {

View file

@ -1,7 +1,7 @@
{
"name": "multi-downloader-nx",
"short_name": "aniDL",
"version": "1.0.4",
"version": "1.1.0",
"description": "Download videos from Funimation or Crunchyroll via cli",
"keywords": [
"download",
@ -29,7 +29,7 @@
"cheerio": "^1.0.0-rc.10",
"form-data": "^4.0.0",
"fs-extra": "^10.0.0",
"got": "^11.7.0",
"got": "^11.8.3",
"hls-download": "^2.6.5",
"iso-639": "^0.2.2",
"lookpath": "^1.1.0",
@ -40,15 +40,15 @@
},
"devDependencies": {
"@types/fs-extra": "^9.0.13",
"@types/node": "^16.11.7",
"@types/yargs": "^17.0.5",
"@typescript-eslint/eslint-plugin": "^5.3.1",
"@typescript-eslint/parser": "^5.3.1",
"@types/node": "^16.11.9",
"@types/yargs": "^17.0.7",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"@typescript-eslint/parser": "^5.4.0",
"eslint": "^7.30.0",
"pkg": "^5.4.1",
"removeNPMAbsolutePaths": "^2.0.0",
"ts-node": "^10.4.0",
"typescript": "^4.4.4"
"typescript": "^4.5.2"
},
"scripts": {
"tsc": "ts-node tsc.ts",