Crunchy multi dubs
This commit is contained in:
parent
768f833644
commit
846baea609
5 changed files with 741 additions and 84 deletions
63
@types/crunchyTypes.d.ts
vendored
63
@types/crunchyTypes.d.ts
vendored
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
644
crunchy.ts
644
crunchy.ts
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
102
package-lock.json
generated
|
|
@ -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": {
|
||||
|
|
|
|||
14
package.json
14
package.json
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Reference in a new issue