mirror of
https://github.com/anidl/multi-downloader-nx.git
synced 2026-03-11 17:45:30 +00:00
Test + Style
This commit is contained in:
parent
255574df1b
commit
2c07f51f54
14 changed files with 1560 additions and 1494 deletions
2
.eslintignore
Normal file
2
.eslintignore
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
lib
|
||||
/videos/*.ts
|
||||
23
.github/workflows/eslint.yml
vendored
23
.github/workflows/eslint.yml
vendored
|
|
@ -1,23 +0,0 @@
|
|||
name: eslint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [14.x]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'npm'
|
||||
- run: npm i
|
||||
- run: npx eslint .
|
||||
33
.github/workflows/test.yml
vendored
Normal file
33
.github/workflows/test.yml
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
name: eslint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
eslint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js 14
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 14
|
||||
cache: 'npm'
|
||||
- run: npm i
|
||||
- run: npx eslint .
|
||||
test:
|
||||
needs: eslint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js 14
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 14
|
||||
cache: 'npm'
|
||||
- run: npm i
|
||||
- run: npm run test
|
||||
|
||||
39
@types/episode.d.ts
vendored
39
@types/episode.d.ts
vendored
|
|
@ -59,20 +59,20 @@ export interface Value {
|
|||
}
|
||||
|
||||
export enum Label {
|
||||
Rating = "Rating",
|
||||
RatingSystem = "Rating System",
|
||||
ReleaseDate = "Release Date",
|
||||
Synopsis = "Synopsis",
|
||||
SynopsisType = "Synopsis Type",
|
||||
Rating = 'Rating',
|
||||
RatingSystem = 'Rating System',
|
||||
ReleaseDate = 'Release Date',
|
||||
Synopsis = 'Synopsis',
|
||||
SynopsisType = 'Synopsis Type',
|
||||
}
|
||||
|
||||
export enum MetaType {
|
||||
Rating = "rating",
|
||||
RatingSystemType = "RatingSystemType",
|
||||
ReleaseDate = "release-date",
|
||||
Synopsis = "synopsis",
|
||||
Synopsistype = "synopsistype",
|
||||
VideoRatingType = "VideoRatingType",
|
||||
Rating = 'rating',
|
||||
RatingSystemType = 'RatingSystemType',
|
||||
ReleaseDate = 'release-date',
|
||||
Synopsis = 'synopsis',
|
||||
Synopsistype = 'synopsistype',
|
||||
VideoRatingType = 'VideoRatingType',
|
||||
}
|
||||
|
||||
export interface HistoricalSelections {
|
||||
|
|
@ -87,8 +87,8 @@ export interface EpisodeDataIDS {
|
|||
}
|
||||
|
||||
export enum TitleElement {
|
||||
Empty = "",
|
||||
English = "English",
|
||||
Empty = '',
|
||||
English = 'English',
|
||||
}
|
||||
|
||||
export interface Media {
|
||||
|
|
@ -194,8 +194,7 @@ export interface AvailIDS {
|
|||
externalAlphaId: string;
|
||||
}
|
||||
|
||||
export interface Next {
|
||||
}
|
||||
export type Next = Record<string, unknown>
|
||||
|
||||
export interface LanguageClass {
|
||||
code: string;
|
||||
|
|
@ -299,7 +298,7 @@ export interface CatalogParent {
|
|||
}
|
||||
|
||||
export enum Source {
|
||||
Dbb = "dbb",
|
||||
Dbb = 'dbb',
|
||||
}
|
||||
|
||||
export interface MetaItems {
|
||||
|
|
@ -313,10 +312,10 @@ export interface Filters {
|
|||
}
|
||||
|
||||
export interface Items {
|
||||
"release-date": AnimationProductionStudio;
|
||||
'release-date': AnimationProductionStudio;
|
||||
rating: AnimationProductionStudio;
|
||||
synopsis: AnimationProductionStudio;
|
||||
"animation-production-studio": AnimationProductionStudio;
|
||||
'animation-production-studio': AnimationProductionStudio;
|
||||
}
|
||||
|
||||
export interface AnimationProductionStudio {
|
||||
|
|
@ -366,8 +365,8 @@ export interface PreviousSeasonEpisode {
|
|||
}
|
||||
|
||||
export enum Type {
|
||||
Episode = "episode",
|
||||
Ova = "ova",
|
||||
Episode = 'episode',
|
||||
Ova = 'ova',
|
||||
}
|
||||
|
||||
export interface Quality {
|
||||
|
|
|
|||
2
@types/hls-download.d.ts
vendored
2
@types/hls-download.d.ts
vendored
|
|
@ -2,7 +2,7 @@ declare module 'hls-download' {
|
|||
export default class hlsDownload {
|
||||
constructor(options: {
|
||||
m3u8json: {
|
||||
segments: {}[],
|
||||
segments: Record<string, unknown>[],
|
||||
mediaSequence?: number,
|
||||
},
|
||||
output?: string,
|
||||
|
|
|
|||
23
@types/items.d.ts
vendored
23
@types/items.d.ts
vendored
|
|
@ -39,8 +39,8 @@ export interface Item {
|
|||
}
|
||||
|
||||
export enum ContentType {
|
||||
Episode = "episode",
|
||||
Ova = "ova",
|
||||
Episode = 'episode',
|
||||
Ova = 'ova',
|
||||
}
|
||||
|
||||
export interface IDs {
|
||||
|
|
@ -110,19 +110,18 @@ export interface MostRecentAvodIDS {
|
|||
}
|
||||
|
||||
export enum Purchase {
|
||||
AVOD = "A-VOD",
|
||||
Dfov = "DFOV",
|
||||
Est = "EST",
|
||||
Svod = "SVOD",
|
||||
AVOD = 'A-VOD',
|
||||
Dfov = 'DFOV',
|
||||
Est = 'EST',
|
||||
Svod = 'SVOD',
|
||||
}
|
||||
|
||||
export enum Version {
|
||||
Simulcast = "Simulcast",
|
||||
Uncut = "Uncut",
|
||||
Simulcast = 'Simulcast',
|
||||
Uncut = 'Uncut',
|
||||
}
|
||||
|
||||
export interface MostRecentSvodJpnUs {
|
||||
}
|
||||
export type MostRecentSvodJpnUs = Record<string, any>
|
||||
|
||||
export interface QualityClass {
|
||||
quality: QualityQuality;
|
||||
|
|
@ -130,8 +129,8 @@ export interface QualityClass {
|
|||
}
|
||||
|
||||
export enum QualityQuality {
|
||||
HD = "HD",
|
||||
SD = "SD",
|
||||
HD = 'HD',
|
||||
SD = 'SD',
|
||||
}
|
||||
|
||||
export interface TitleImages {
|
||||
|
|
|
|||
16
@types/m3u8-parsed.d.ts
vendored
16
@types/m3u8-parsed.d.ts
vendored
|
|
@ -32,17 +32,17 @@ declare module 'm3u8-parsed' {
|
|||
uri: string,
|
||||
timeline: number,
|
||||
attributes: {
|
||||
"CLOSED-CAPTIONS": string,
|
||||
"AUDIO": string,
|
||||
"FRAME-RATE": number,
|
||||
"RESOLUTION": {
|
||||
'CLOSED-CAPTIONS': string,
|
||||
'AUDIO': string,
|
||||
'FRAME-RATE': number,
|
||||
'RESOLUTION': {
|
||||
width: number,
|
||||
height: number
|
||||
},
|
||||
"CODECS": string,
|
||||
"AVERAGE-BANDWIDTH": string,
|
||||
"BANDWIDTH": number
|
||||
'CODECS': string,
|
||||
'AVERAGE-BANDWIDTH': string,
|
||||
'BANDWIDTH': number
|
||||
}
|
||||
}[],
|
||||
}
|
||||
};
|
||||
}
|
||||
182
funi.ts
182
funi.ts
|
|
@ -22,11 +22,11 @@ import * as yamlCfg from './modules/module.cfg-loader';
|
|||
import vttConvert from './modules/module.vttconvert';
|
||||
|
||||
// types
|
||||
import { Item } from "./@types/items";
|
||||
import { Item } from './@types/items';
|
||||
|
||||
// params
|
||||
const cfg = yamlCfg.loadCfg();
|
||||
let token = yamlCfg.loadFuniToken();
|
||||
const token = yamlCfg.loadFuniToken();
|
||||
// cli
|
||||
const argv = appYargs.appArgv(cfg.cli);
|
||||
|
||||
|
|
@ -75,11 +75,11 @@ let title = '',
|
|||
|
||||
// auth
|
||||
async function auth(){
|
||||
let authOpts = {
|
||||
const authOpts = {
|
||||
user: await shlp.question('[Q] LOGIN/EMAIL'),
|
||||
pass: await shlp.question('[Q] PASSWORD ')
|
||||
};
|
||||
let authData = await getData({
|
||||
const authData = await getData({
|
||||
baseUrl: api_host,
|
||||
url: '/auth/login/',
|
||||
auth: authOpts,
|
||||
|
|
@ -102,8 +102,8 @@ async function auth(){
|
|||
|
||||
// search show
|
||||
async function searchShow(){
|
||||
let qs = {unique: true, limit: 100, q: argv.search, offset: 0 };
|
||||
let searchData = await getData({
|
||||
const qs = {unique: true, limit: 100, q: argv.search, offset: 0 };
|
||||
const searchData = await getData({
|
||||
baseUrl: api_host,
|
||||
url: '/source/funimation/search/auto/',
|
||||
querystring: qs,
|
||||
|
|
@ -118,9 +118,9 @@ async function searchShow(){
|
|||
return;
|
||||
}
|
||||
if(searchDataJSON.items && searchDataJSON.items.hits){
|
||||
let shows = searchDataJSON.items.hits;
|
||||
const shows = searchDataJSON.items.hits;
|
||||
console.log('[INFO] Search Results:');
|
||||
for(let ssn in shows){
|
||||
for(const ssn in shows){
|
||||
console.log(`[#${shows[ssn].id}] ${shows[ssn].title}` + (shows[ssn].tx_date?` (${shows[ssn].tx_date})`:''));
|
||||
}
|
||||
}
|
||||
|
|
@ -130,7 +130,7 @@ async function searchShow(){
|
|||
// get show
|
||||
async function getShow(){
|
||||
// show main data
|
||||
let showData = await getData({
|
||||
const showData = await getData({
|
||||
baseUrl: api_host,
|
||||
url: `/source/catalog/title/${argv.s}`,
|
||||
token: token,
|
||||
|
|
@ -151,7 +151,7 @@ async function getShow(){
|
|||
const showDataItem = showDataJSON.items[0];
|
||||
console.log('[#%s] %s (%s)',showDataItem.id,showDataItem.title,showDataItem.releaseYear);
|
||||
// show episodes
|
||||
let qs: {
|
||||
const qs: {
|
||||
limit: number,
|
||||
sort: string,
|
||||
sort_direction: string,
|
||||
|
|
@ -159,7 +159,7 @@ async function getShow(){
|
|||
language?: string
|
||||
} = { limit: -1, sort: 'order', sort_direction: 'ASC', title_id: argv.s as number };
|
||||
if(argv.alt){ qs.language = 'English'; }
|
||||
let episodesData = await getData({
|
||||
const episodesData = await getData({
|
||||
baseUrl: api_host,
|
||||
url: '/funimation/episodes/',
|
||||
querystring: qs,
|
||||
|
|
@ -170,14 +170,14 @@ async function getShow(){
|
|||
if(!episodesData.ok || !episodesData.res){return;}
|
||||
|
||||
let epsDataArr: Item[] = JSON.parse(episodesData.res.body).items;
|
||||
let epNumRegex = /^([A-Z0-9]*[A-Z])?(\d+)$/i;
|
||||
let epSelEpsTxt = [], epSelList, typeIdLen = 0, epIdLen = 4;
|
||||
const epNumRegex = /^([A-Z0-9]*[A-Z])?(\d+)$/i;
|
||||
const epSelEpsTxt = []; let typeIdLen = 0, epIdLen = 4;
|
||||
|
||||
const parseEpStr = (epStr: string) => {
|
||||
const match = epStr.match(epNumRegex);
|
||||
if (!match) {
|
||||
console.error('[ERROR] No match found')
|
||||
return ['', '']
|
||||
console.error('[ERROR] No match found');
|
||||
return ['', ''];
|
||||
}
|
||||
if(match.length > 2){
|
||||
const spliced = [...match].splice(1);
|
||||
|
|
@ -204,14 +204,14 @@ async function getShow(){
|
|||
return e;
|
||||
});
|
||||
|
||||
epSelList = parseSelect(argv.e as string);
|
||||
const epSelList = parseSelect(argv.e as string);
|
||||
|
||||
let fnSlug: {
|
||||
const fnSlug: {
|
||||
title: string,
|
||||
episode: string
|
||||
}[] = [], is_selected = false;
|
||||
}[] = []; let is_selected = false;
|
||||
|
||||
let eps = epsDataArr;
|
||||
const eps = epsDataArr;
|
||||
epsDataArr.sort((a, b) => {
|
||||
if (a.item.seasonOrder < b.item.seasonOrder && a.id.localeCompare(b.id) < 0) {
|
||||
return -1;
|
||||
|
|
@ -222,7 +222,7 @@ async function getShow(){
|
|||
return 0;
|
||||
});
|
||||
|
||||
for(let e in eps){
|
||||
for(const e in eps){
|
||||
eps[e].id_split[1] = parseInt(eps[e].id_split[1].toString()).toString().padStart(epIdLen, '0');
|
||||
let epStrId = eps[e].id_split.join('');
|
||||
// select
|
||||
|
|
@ -236,13 +236,13 @@ async function getShow(){
|
|||
is_selected = false;
|
||||
}
|
||||
// console vars
|
||||
let tx_snum = eps[e].item.seasonNum=='1'?'':` S${eps[e].item.seasonNum}`;
|
||||
let tx_type = eps[e].mediaCategory != 'episode' ? eps[e].mediaCategory : '';
|
||||
let tx_enum = eps[e].item.episodeNum && eps[e].item.episodeNum !== '' ?
|
||||
const tx_snum = eps[e].item.seasonNum=='1'?'':` S${eps[e].item.seasonNum}`;
|
||||
const tx_type = eps[e].mediaCategory != 'episode' ? eps[e].mediaCategory : '';
|
||||
const tx_enum = eps[e].item.episodeNum && eps[e].item.episodeNum !== '' ?
|
||||
`#${(parseInt(eps[e].item.episodeNum) < 10 ? '0' : '')+eps[e].item.episodeNum}` : '#'+eps[e].item.episodeId;
|
||||
let qua_str = eps[e].quality.height ? eps[e].quality.quality + eps[e].quality.height : 'UNK';
|
||||
let aud_str = eps[e].audio.length > 0 ? `, ${eps[e].audio.join(', ')}` : '';
|
||||
let rtm_str = eps[e].item.runtime !== '' ? eps[e].item.runtime : '??:??';
|
||||
const qua_str = eps[e].quality.height ? eps[e].quality.quality + eps[e].quality.height : 'UNK';
|
||||
const aud_str = eps[e].audio.length > 0 ? `, ${eps[e].audio.join(', ')}` : '';
|
||||
const rtm_str = eps[e].item.runtime !== '' ? eps[e].item.runtime : '??:??';
|
||||
// console string
|
||||
eps[e].id_split[0] = eps[e].id_split[0].toString().padStart(typeIdLen, ' ');
|
||||
epStrId = eps[e].id_split.join('');
|
||||
|
|
@ -270,7 +270,7 @@ async function getEpisode(fnSlug: {
|
|||
title: string,
|
||||
episode: string
|
||||
}) {
|
||||
let episodeData = await getData({
|
||||
const episodeData = await getData({
|
||||
baseUrl: api_host,
|
||||
url: `/source/catalog/episode/${fnSlug.title}/${fnSlug.episode}/`,
|
||||
token: token,
|
||||
|
|
@ -278,7 +278,7 @@ async function getEpisode(fnSlug: {
|
|||
debug: argv.debug,
|
||||
});
|
||||
if(!episodeData.ok || !episodeData.res){return;}
|
||||
let ep = JSON.parse(episodeData.res.body).items[0] as EpisodeData, streamIds = [];
|
||||
const ep = JSON.parse(episodeData.res.body).items[0] as EpisodeData, streamIds = [];
|
||||
// build fn
|
||||
showTitle = ep.parent.title;
|
||||
title = ep.title;
|
||||
|
|
@ -289,7 +289,7 @@ async function getEpisode(fnSlug: {
|
|||
fnEpNum = isNaN(parseInt(ep.number)) ? ep.number : parseInt(ep.number);
|
||||
|
||||
// is uncut
|
||||
let uncut = {
|
||||
const uncut = {
|
||||
Japanese: false,
|
||||
English: false
|
||||
};
|
||||
|
|
@ -306,7 +306,7 @@ async function getEpisode(fnSlug: {
|
|||
console.log('[INFO] Available streams (Non-Encrypted):');
|
||||
|
||||
// map medias
|
||||
let media = ep.media.map(function(m){
|
||||
const media = ep.media.map(function(m){
|
||||
if(m.mediaType == 'experience'){
|
||||
if(m.version.match(/uncut/i) && m.language){
|
||||
uncut[m.language] = true;
|
||||
|
|
@ -334,28 +334,22 @@ async function getEpisode(fnSlug: {
|
|||
|
||||
// select
|
||||
stDlPath = [];
|
||||
for(let m of media){
|
||||
for(const m of media){
|
||||
let selected = false;
|
||||
if(m.id > 0 && m.type == 'Non-Encrypted'){
|
||||
let dub_type = m.language;
|
||||
const dub_type = m.language;
|
||||
if (!dub_type)
|
||||
continue;
|
||||
let localSubs: Subtitle[] = [];
|
||||
let selUncut = !argv.simul && uncut[dub_type] && m.version?.match(/uncut/i)
|
||||
const selUncut = !argv.simul && uncut[dub_type] && m.version?.match(/uncut/i)
|
||||
? true
|
||||
: (!uncut[dub_type] || argv.simul && m.version?.match(/simulcast/i) ? true : false);
|
||||
for (let curDub of (argv.dub as appYargs.possibleDubs)) {
|
||||
for (const curDub of (argv.dub as appYargs.possibleDubs)) {
|
||||
if(dub_type == dubType[curDub] && selUncut){
|
||||
streamIds.push({
|
||||
id: m.id,
|
||||
lang: merger.getLanguageCode(curDub, curDub.slice(0, -2))
|
||||
});
|
||||
if (!m.subtitles) {
|
||||
console.log('[ERROR] Unable to find subs for episode ', m.id)
|
||||
if (argv.debug)
|
||||
console.log(m)
|
||||
continue;
|
||||
}
|
||||
stDlPath.push(...m.subtitles);
|
||||
localSubs = m.subtitles;
|
||||
selected = true;
|
||||
|
|
@ -367,7 +361,7 @@ async function getEpisode(fnSlug: {
|
|||
}
|
||||
}
|
||||
|
||||
let already: string[] = [];
|
||||
const already: string[] = [];
|
||||
stDlPath = stDlPath.filter(a => {
|
||||
if (already.includes(a.language)) {
|
||||
return false;
|
||||
|
|
@ -382,8 +376,8 @@ async function getEpisode(fnSlug: {
|
|||
}
|
||||
else{
|
||||
tsDlPath = [];
|
||||
for (let streamId of streamIds) {
|
||||
let streamData = await getData({
|
||||
for (const streamId of streamIds) {
|
||||
const streamData = await getData({
|
||||
baseUrl: api_host,
|
||||
url: `/source/catalog/video/${streamId.id}/signed`,
|
||||
token: token,
|
||||
|
|
@ -398,7 +392,7 @@ async function getEpisode(fnSlug: {
|
|||
return;
|
||||
}
|
||||
else{
|
||||
for(let u in streamDataRes.items){
|
||||
for(const u in streamDataRes.items){
|
||||
if(streamDataRes.items[u].videoType == 'm3u8'){
|
||||
tsDlPath.push({
|
||||
path: streamDataRes.items[u].src,
|
||||
|
|
@ -432,18 +426,18 @@ function getSubsUrl(m: MediaChild[]) : Subtitle[] {
|
|||
'ptBR': 'Portuguese (Brazil)'
|
||||
};
|
||||
|
||||
let subLangAvailable = m.some(a => subLangs.some(subLang => a.ext == 'vtt' && a.language === subType[subLang]));
|
||||
const subLangAvailable = m.some(a => subLangs.some(subLang => a.ext == 'vtt' && a.language === subType[subLang]));
|
||||
|
||||
if (!subLangAvailable) {
|
||||
subLangs = [ 'enUS' ];
|
||||
}
|
||||
|
||||
let found: Subtitle[] = [];
|
||||
const found: Subtitle[] = [];
|
||||
|
||||
for(let i in m){
|
||||
let fpp = m[i].filePath.split('.');
|
||||
let fpe = fpp[fpp.length-1];
|
||||
for (let lang of subLangs) {
|
||||
for(const i in m){
|
||||
const fpp = m[i].filePath.split('.');
|
||||
const fpe = fpp[fpp.length-1];
|
||||
for (const lang of subLangs) {
|
||||
if(fpe == 'vtt' && m[i].language === subType[lang]) {
|
||||
found.push({
|
||||
path: m[i].filePath,
|
||||
|
|
@ -462,26 +456,26 @@ async function downloadStreams(){
|
|||
|
||||
// req playlist
|
||||
|
||||
let purvideo: DownloadedFile[] = [];
|
||||
let puraudio: DownloadedFile[] = [];
|
||||
let audioAndVideo: DownloadedFile[] = [];
|
||||
for (let streamPath of tsDlPath) {
|
||||
let plQualityReq = await getData({
|
||||
const purvideo: DownloadedFile[] = [];
|
||||
const puraudio: DownloadedFile[] = [];
|
||||
const audioAndVideo: DownloadedFile[] = [];
|
||||
for (const streamPath of tsDlPath) {
|
||||
const plQualityReq = await getData({
|
||||
url: streamPath.path,
|
||||
debug: argv.debug,
|
||||
});
|
||||
if(!plQualityReq.ok || !plQualityReq.res){return;}
|
||||
|
||||
let plQualityLinkList = m3u8(plQualityReq.res.body);
|
||||
const plQualityLinkList = m3u8(plQualityReq.res.body);
|
||||
|
||||
let mainServersList = [
|
||||
const mainServersList = [
|
||||
'vmfst-api.prd.funimationsvc.com',
|
||||
'd33et77evd9bgg.cloudfront.net',
|
||||
'd132fumi6di1wa.cloudfront.net',
|
||||
'funiprod.akamaized.net',
|
||||
];
|
||||
|
||||
let plServerList: string[] = [],
|
||||
const plServerList: string[] = [],
|
||||
plStreams: Record<string|number, {
|
||||
[key: string]: string
|
||||
}> = {},
|
||||
|
|
@ -489,8 +483,8 @@ async function downloadStreams(){
|
|||
plLayersRes: Record<string|number, {
|
||||
width: number,
|
||||
height: number
|
||||
}> = {},
|
||||
plMaxLayer = 1,
|
||||
}> = {};
|
||||
let plMaxLayer = 1,
|
||||
plNewIds = 1,
|
||||
plAud: {
|
||||
uri: string,
|
||||
|
|
@ -499,11 +493,11 @@ async function downloadStreams(){
|
|||
} = { uri: '', langStr: '', language: '' };
|
||||
|
||||
// new uris
|
||||
let vplReg = /streaming_video_(\d+)_(\d+)_(\d+)_index\.m3u8/;
|
||||
const vplReg = /streaming_video_(\d+)_(\d+)_(\d+)_index\.m3u8/;
|
||||
if(plQualityLinkList.playlists[0].uri.match(vplReg)){
|
||||
let audioKey = Object.keys(plQualityLinkList.mediaGroups.AUDIO).pop();
|
||||
const audioKey = Object.keys(plQualityLinkList.mediaGroups.AUDIO).pop();
|
||||
if (!audioKey)
|
||||
return console.log('[ERROR] No audio key found')
|
||||
return console.log('[ERROR] No audio key found');
|
||||
if(plQualityLinkList.mediaGroups.AUDIO[audioKey]){
|
||||
const audioDataParts = plQualityLinkList.mediaGroups.AUDIO[audioKey],
|
||||
audioEl = Object.keys(audioDataParts);
|
||||
|
|
@ -513,11 +507,11 @@ async function downloadStreams(){
|
|||
plQualityLinkList.playlists.sort((a, b) => {
|
||||
const aMatch = a.uri.match(vplReg), bMatch = b.uri.match(vplReg);
|
||||
if (!aMatch || !bMatch) {
|
||||
console.log('[ERROR] Unable to match')
|
||||
console.log('[ERROR] Unable to match');
|
||||
return 0;
|
||||
}
|
||||
let av = parseInt(aMatch[3]);
|
||||
let bv = parseInt(bMatch[3]);
|
||||
const av = parseInt(aMatch[3]);
|
||||
const bv = parseInt(bMatch[3]);
|
||||
if(av > bv){
|
||||
return 1;
|
||||
}
|
||||
|
|
@ -528,7 +522,7 @@ async function downloadStreams(){
|
|||
});
|
||||
}
|
||||
|
||||
for(let s of plQualityLinkList.playlists){
|
||||
for(const s of plQualityLinkList.playlists){
|
||||
if(s.uri.match(/_Layer(\d+)\.m3u8/) || s.uri.match(vplReg)){
|
||||
// set layer and max layer
|
||||
let plLayerId: number|string = 0;
|
||||
|
|
@ -541,8 +535,8 @@ async function downloadStreams(){
|
|||
}
|
||||
plMaxLayer = plMaxLayer < plLayerId ? plLayerId : plMaxLayer;
|
||||
// set urls and servers
|
||||
let plUrlDl = s.uri;
|
||||
let plServer = new URL(plUrlDl).host;
|
||||
const plUrlDl = s.uri;
|
||||
const plServer = new URL(plUrlDl).host;
|
||||
if(!plServerList.includes(plServer)){
|
||||
plServerList.push(plServer);
|
||||
}
|
||||
|
|
@ -556,15 +550,15 @@ async function downloadStreams(){
|
|||
plStreams[plServer][plLayerId] = plUrlDl;
|
||||
}
|
||||
// set plLayersStr
|
||||
let plResolution = s.attributes.RESOLUTION;
|
||||
const plResolution = s.attributes.RESOLUTION;
|
||||
plLayersRes[plLayerId] = plResolution;
|
||||
let plBandwidth = Math.round(s.attributes.BANDWIDTH/1024);
|
||||
const plBandwidth = Math.round(s.attributes.BANDWIDTH/1024);
|
||||
if(plLayerId<10){
|
||||
plLayerId = plLayerId.toString().padStart(2,' ');
|
||||
}
|
||||
let qualityStrAdd = `${plLayerId}: ${plResolution.width}x${plResolution.height} (${plBandwidth}KiB/s)`;
|
||||
let qualityStrRegx = new RegExp(qualityStrAdd.replace(/(:|\(|\)|\/)/g,'\\$1'),'m');
|
||||
let qualityStrMatch = !plLayersStr.join('\r\n').match(qualityStrRegx);
|
||||
const qualityStrAdd = `${plLayerId}: ${plResolution.width}x${plResolution.height} (${plBandwidth}KiB/s)`;
|
||||
const qualityStrRegx = new RegExp(qualityStrAdd.replace(/(:|\(|\)|\/)/g,'\\$1'),'m');
|
||||
const qualityStrMatch = !plLayersStr.join('\r\n').match(qualityStrRegx);
|
||||
if(qualityStrMatch){
|
||||
plLayersStr.push(qualityStrAdd);
|
||||
}
|
||||
|
|
@ -574,7 +568,7 @@ async function downloadStreams(){
|
|||
}
|
||||
}
|
||||
|
||||
for(let s of mainServersList){
|
||||
for(const s of mainServersList){
|
||||
if(plServerList.includes(s)){
|
||||
plServerList.splice(plServerList.indexOf(s), 1);
|
||||
plServerList.unshift(s);
|
||||
|
|
@ -585,9 +579,9 @@ async function downloadStreams(){
|
|||
|
||||
argv.q = argv.q < 1 || argv.q > plMaxLayer ? plMaxLayer : argv.q;
|
||||
|
||||
let plSelectedServer = plServerList[argv.x-1];
|
||||
let plSelectedList = plStreams[plSelectedServer];
|
||||
let videoUrl = argv.x < plServerList.length+1 && plSelectedList[argv.q] ? plSelectedList[argv.q] : '';
|
||||
const plSelectedServer = plServerList[argv.x-1];
|
||||
const plSelectedList = plStreams[plSelectedServer];
|
||||
const videoUrl = argv.x < plServerList.length+1 && plSelectedList[argv.q] ? plSelectedList[argv.q] : '';
|
||||
|
||||
plLayersStr.sort();
|
||||
console.log(`[INFO] Servers available:\n\t${plServerList.join('\n\t')}`);
|
||||
|
|
@ -623,15 +617,15 @@ async function downloadStreams(){
|
|||
break video;
|
||||
}
|
||||
// download video
|
||||
let reqVideo = await getData({
|
||||
const reqVideo = await getData({
|
||||
url: videoUrl,
|
||||
debug: argv.debug,
|
||||
});
|
||||
if (!reqVideo.ok || !reqVideo.res) { break video; }
|
||||
|
||||
let chunkList = m3u8(reqVideo.res.body);
|
||||
const chunkList = m3u8(reqVideo.res.body);
|
||||
|
||||
let tsFile = path.join(cfg.dir.content, ...fnOutput.slice(0, -1), `${fnOutput.slice(-1)}.video${(plAud.uri ? '' : '.' + streamPath.lang )}`);
|
||||
const tsFile = path.join(cfg.dir.content, ...fnOutput.slice(0, -1), `${fnOutput.slice(-1)}.video${(plAud.uri ? '' : '.' + streamPath.lang )}`);
|
||||
dlFailed = !await downloadFile(tsFile, chunkList);
|
||||
if (!dlFailed) {
|
||||
if (plAud.uri) {
|
||||
|
|
@ -654,15 +648,15 @@ async function downloadStreams(){
|
|||
// download audio
|
||||
if (audioAndVideo.some(a => a.lang === plAud.language) || puraudio.some(a => a.lang === plAud.language))
|
||||
break audio;
|
||||
let reqAudio = await getData({
|
||||
const reqAudio = await getData({
|
||||
url: plAud.uri,
|
||||
debug: argv.debug,
|
||||
});
|
||||
if (!reqAudio.ok || !reqAudio.res) { return; }
|
||||
|
||||
let chunkListA = m3u8(reqAudio.res.body);
|
||||
const chunkListA = m3u8(reqAudio.res.body);
|
||||
|
||||
let tsFileA = path.join(cfg.dir.content, ...fnOutput.slice(0, -1), `${fnOutput.slice(-1)}.audio.${plAud.language}`);
|
||||
const tsFileA = path.join(cfg.dir.content, ...fnOutput.slice(0, -1), `${fnOutput.slice(-1)}.audio.${plAud.language}`);
|
||||
|
||||
dlFailedA = !await downloadFile(tsFileA, chunkListA);
|
||||
if (!dlFailedA)
|
||||
|
|
@ -675,19 +669,19 @@ async function downloadStreams(){
|
|||
}
|
||||
|
||||
// add subs
|
||||
let subsExt = !argv.mp4 || argv.mp4 && argv.ass ? '.ass' : '.srt';
|
||||
const subsExt = !argv.mp4 || argv.mp4 && argv.ass ? '.ass' : '.srt';
|
||||
let addSubs = true;
|
||||
|
||||
// download subtitles
|
||||
if(stDlPath.length > 0){
|
||||
console.log('[INFO] Downloading subtitles...');
|
||||
for (let subObject of stDlPath) {
|
||||
let subsSrc = await getData({
|
||||
for (const subObject of stDlPath) {
|
||||
const subsSrc = await getData({
|
||||
url: subObject.path,
|
||||
debug: argv.debug,
|
||||
});
|
||||
if(subsSrc.ok && subsSrc.res){
|
||||
let assData = vttConvert(subsSrc.res.body, (subsExt == '.srt' ? true : false), subObject.langName, argv.fontSize);
|
||||
const assData = vttConvert(subsSrc.res.body, (subsExt == '.srt' ? true : false), subObject.langName, argv.fontSize);
|
||||
subObject.file = path.join(cfg.dir.content, ...fnOutput.slice(0, -1), `${fnOutput.slice(-1)}.subtitle${subObject.ext}${subsExt}`);
|
||||
fs.writeFileSync(subObject.file, assData);
|
||||
}
|
||||
|
|
@ -734,14 +728,14 @@ async function downloadStreams(){
|
|||
subtitels: stDlPath as SubtitleInput[],
|
||||
videoAndAudio: audioAndVideo,
|
||||
simul: argv.simul
|
||||
})
|
||||
});
|
||||
|
||||
if(!argv.mp4 && mergerBin.MKVmerge){
|
||||
let command = mergeInstance.MkvMerge();
|
||||
const command = mergeInstance.MkvMerge();
|
||||
shlp.exec('mkvmerge', `"${mergerBin.MKVmerge}"`, command);
|
||||
}
|
||||
else if(mergerBin.FFmpeg){
|
||||
let command = mergeInstance.FFmpeg();
|
||||
const command = mergeInstance.FFmpeg();
|
||||
shlp.exec('ffmpeg',`"${mergerBin.FFmpeg}"`,command);
|
||||
}
|
||||
else{
|
||||
|
|
@ -757,7 +751,7 @@ async function downloadStreams(){
|
|||
}
|
||||
|
||||
async function downloadFile(filename: string, chunkList: {
|
||||
segments: {}[],
|
||||
segments: Record<string, unknown>[],
|
||||
}) {
|
||||
const downloadStatus = await new hlsDownload({
|
||||
m3u8json: chunkList,
|
||||
|
|
@ -782,7 +776,7 @@ function parseFileName(input: string, title: string, episode:number|string, show
|
|||
break;
|
||||
case 'episode': {
|
||||
if (typeof episode === 'number') {
|
||||
let len = episode.toFixed(0).toString().length;
|
||||
const len = episode.toFixed(0).toString().length;
|
||||
input = input.replace(vars[i], len < argv.numbers ? '0'.repeat(argv.numbers - len) + episode : episode.toString());
|
||||
} else {
|
||||
input = input.replace(vars[i], episode);
|
||||
|
|
@ -793,7 +787,7 @@ function parseFileName(input: string, title: string, episode:number|string, show
|
|||
input = input.replace(vars[i], showTitle);
|
||||
break;
|
||||
case 'season': {
|
||||
let len = season.toFixed(0).toString().length;
|
||||
const len = season.toFixed(0).toString().length;
|
||||
input = input.replace(vars[i], len < argv.numbers ? '0'.repeat(argv.numbers - len) + season : season.toString());
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import fs from 'fs-extra';
|
|||
import pkg from '../package.json';
|
||||
import modulesCleanup from 'removeNPMAbsolutePaths';
|
||||
import { exec } from 'pkg';
|
||||
import { execSync } from 'child_process';
|
||||
|
||||
const buildsDir = './_builds';
|
||||
const nodeVer = 'node14-';
|
||||
|
|
@ -50,13 +51,13 @@ const nodeVer = 'node14-';
|
|||
fs.copySync('./config/dir-path.yml', `${buildDir}/config/dir-path.yml`);
|
||||
fs.copySync('./modules/cmd-here.bat', `${buildDir}/cmd-here.bat`);
|
||||
fs.copySync('./modules/NotoSans-Regular.ttf', `${buildDir}/NotoSans-Regular.ttf`);
|
||||
fs.copySync('./package.json', `${buildDir}/package.json`)
|
||||
fs.copySync('./package.json', `${buildDir}/package.json`);
|
||||
fs.copySync('./docs/', `${buildDir}/docs/`);
|
||||
fs.copySync('./LICENSE.md', `${buildDir}/docs/LICENSE.md`);
|
||||
if(fs.existsSync(`${buildsDir}/${buildFull}.7z`)){
|
||||
fs.removeSync(`${buildsDir}/${buildFull}.7z`);
|
||||
}
|
||||
require('child_process').execSync(`7z a -t7z "${buildsDir}/${buildFull}.7z" "${buildDir}"`,{stdio:[0,1,2]});
|
||||
execSync(`7z a -t7z "${buildsDir}/${buildFull}.7z" "${buildDir}"`,{stdio:[0,1,2]});
|
||||
}());
|
||||
|
||||
function getTarget(bt: string) : string {
|
||||
|
|
|
|||
|
|
@ -243,7 +243,7 @@ const appArgv = (cfg: {
|
|||
argv.dub = dubLang;
|
||||
if (argv.allSubs)
|
||||
argv.subLang = subLang;
|
||||
for (let key in argv) {
|
||||
for (const key in argv) {
|
||||
if (argv[key] instanceof Array && !(key === 'subLang' || key === 'dub')) {
|
||||
argv[key] = (argv[key] as Array<unknown>).pop();
|
||||
}
|
||||
|
|
|
|||
959
package-lock.json
generated
959
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "funimation-downloader-nx",
|
||||
"short_name": "funi",
|
||||
"version": "4.12.5",
|
||||
"version": "5.0.0",
|
||||
"description": "Download videos from Funimation via cli.",
|
||||
"keywords": [
|
||||
"download",
|
||||
|
|
@ -57,6 +57,8 @@
|
|||
"build-macos64": "cd lib && node modules/build macos64",
|
||||
"eslint": "eslint *.js modules",
|
||||
"eslint-fix": "eslint *.js modules --fix",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
"pretest": "npm run tsc",
|
||||
"test": "cd lib && node modules/build win64 && node modules/build linux64 && node modules/build macos64"
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
32
tsc.ts
32
tsc.ts
|
|
@ -1,7 +1,7 @@
|
|||
import { exec } from "child_process";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { removeSync, copyFileSync } from "fs-extra";
|
||||
import { exec } from 'child_process';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { removeSync, copyFileSync } from 'fs-extra';
|
||||
|
||||
const ignore = [
|
||||
'.git',
|
||||
|
|
@ -13,29 +13,29 @@ const ignore = [
|
|||
(async () => {
|
||||
removeSync('lib');
|
||||
const tsc = exec('npx tsc');
|
||||
tsc.stdout?.on("data", console.log);
|
||||
tsc.stderr?.on("data", console.log);
|
||||
tsc.stdout?.on('data', console.log);
|
||||
tsc.stderr?.on('data', console.log);
|
||||
|
||||
tsc.on("close", () => {
|
||||
tsc.on('close', () => {
|
||||
const files = readDir(__dirname);
|
||||
const filtered = files.filter(a => {
|
||||
if (a.stats.isFile()) {
|
||||
return a.path.split('.').pop() !== 'ts';
|
||||
} else {
|
||||
return true
|
||||
return true;
|
||||
}
|
||||
})
|
||||
});
|
||||
filtered.forEach(item => {
|
||||
const itemPath = path.join(__dirname, 'lib', item.path.replace(__dirname, ''));
|
||||
if (item.stats.isDirectory()) {
|
||||
if (!fs.existsSync(itemPath))
|
||||
fs.mkdirSync(itemPath)
|
||||
fs.mkdirSync(itemPath);
|
||||
} else {
|
||||
copyFileSync(item.path, itemPath)
|
||||
copyFileSync(item.path, itemPath);
|
||||
}
|
||||
})
|
||||
})
|
||||
})()
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
||||
const readDir = (dir: string) : {
|
||||
path: string,
|
||||
|
|
@ -56,8 +56,8 @@ const readDir = (dir: string) : {
|
|||
stats
|
||||
});
|
||||
if (stats.isDirectory()) {
|
||||
items.push(...readDir(itemPath))
|
||||
items.push(...readDir(itemPath));
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
};
|
||||
Loading…
Reference in a new issue