This commit is contained in:
Izuco 2021-10-31 16:08:20 +01:00
parent 58697e8b0f
commit 22b67b45d5
No known key found for this signature in database
GPG key ID: 318460063D70949F
7 changed files with 224 additions and 54 deletions

View file

@ -3,7 +3,8 @@ export type CrunchyEpMeta = {
seasonTitle: string,
episodeNumber: string,
episodeTitle: string,
playback?: string
playback?: string,
seasonID: string
}
export type ParseItem = {

97
@types/objectInfo.d.ts vendored Normal file
View file

@ -0,0 +1,97 @@
// Generated by https://quicktype.io
export interface ObjectInfo {
__class__: string;
__href__: string;
__resource_key__: string;
__links__: Actions;
__actions__: Actions;
total: number;
items: Item[];
}
export interface Actions {
}
export interface Item {
__class__: string;
__href__: string;
__links__: Links;
__actions__: Actions;
id: string;
external_id: string;
channel_id: string;
title: string;
description: string;
promo_title: string;
promo_description: string;
type: string;
slug: string;
slug_title: string;
images: Images;
episode_metadata: EpisodeMetadata;
playback: string;
linked_resource_key: string;
type: string;
s_num?: string;
f_num?: string;
movie_metadata?: {
movie_listing_id: string;
movie_listing_title: string
};
isSelected?: boolean
}
export interface Links {
"episode/season": EpisodeSeason;
"episode/series": EpisodeSeason;
resource: EpisodeSeason;
"resource/channel": EpisodeSeason;
streams: EpisodeSeason;
}
export interface EpisodeSeason {
href: string;
}
export interface EpisodeMetadata {
series_id: string;
series_title: string;
series_slug_title: string;
season_id: string;
season_title: string;
season_slug_title: string;
season_number: number;
episode_number: number;
episode: string;
sequence_number: number;
duration_ms: number;
ad_breaks: AdBreak[];
episode_air_date: string;
is_premium_only: boolean;
is_mature: boolean;
mature_blocked: boolean;
is_subbed: boolean;
is_dubbed: boolean;
is_clip: boolean;
available_offline: boolean;
maturity_ratings: string[];
subtitle_locales: string[];
availability_notes: string;
}
export interface AdBreak {
type: string;
offset_ms: number;
}
export interface Images {
thumbnail: Array<Thumbnail[]>;
}
export interface Thumbnail {
width: number;
height: number;
type: string;
source: string;
}

34
@types/playbackData.d.ts vendored Normal file
View file

@ -0,0 +1,34 @@
// Generated by https://quicktype.io
export interface PlaybackData {
audio_locale: string;
subtitles: { [key: string]: Subtitle };
streams: { [key: string]: { [key: string]: Stream } };
QoS: QoS;
}
export interface QoS {
region: string;
cloudFrontRequestId: string;
lambdaRunTime: number;
}
export interface Stream {
hardsub_locale: string;
url: string;
vcodec: Vcodec;
hardsub_lang?: string;
audio_lang?: string;
type?: string;
}
export enum Vcodec {
H264 = "h264",
}
export interface Subtitle {
locale: Locale;
url: string;
format: string;
}

View file

@ -30,7 +30,13 @@ let cmsToken: {
// args
const argv = yargs.appArgv(cfg.cli)
argv.appstore = {};
const appstore: {
fn: Variable[],
isBatch: boolean
} = {
fn: [],
isBatch: false
}
// load req
import { domain, api } from './modules/module.api-urls';
@ -38,6 +44,9 @@ import * as reqModule from './modules/module.req';
import { CrunchySearch, ItemItem, ItemType } from './@types/crunchySearch';
import { CrunchyEpisodeList } from './@types/crunchyEpisodeList';
import { CrunchyEpMeta, ParseItem } from './@types/crunchyTypes';
import { ObjectInfo } from './@types/objectInfo';
import { Variable } from './modules/module.filename';
import { PlaybackData } from './@types/playbackData';
const req = new reqModule.Req(domain, argv);
// select
@ -654,6 +663,7 @@ async function getSeasonById(){
seasonTitle: item.season_title,
episodeNumber: item.episode,
episodeTitle: item.title,
seasonID: item.season_id
};
if(item.playback){
epMeta.playback = item.playback;
@ -690,7 +700,7 @@ async function getSeasonById(){
}
if(selectedMedia.length > 1){
(argv.appstore as Record<string, unknown>).isBatch = true;
appstore.isBatch = true;
}
console.log();
@ -700,14 +710,14 @@ async function getSeasonById(){
}
async function getObjectById(returnData){
async function getObjectById(returnData?: boolean){
if(!cmsToken.cms){
console.log('[ERROR] Authentication required!');
return;
}
const doEpsFilter = new epsFilter.doFilter();
const inpMedia = doEpsFilter.checkBetaFilter(argv.episode);
const inpMedia = doEpsFilter.checkBetaFilter(argv.e);
if(inpMedia.length < 1){
console.log('\n[INFO] Objects not selected!\n');
@ -729,11 +739,11 @@ async function getObjectById(returnData){
'Key-Pair-Id': cmsToken.cms.key_pair_id,
}),
].join('');
const objectReq = await req.getData(objectReqOpts, {useProxy: true});
if(!objectReq.ok){
const objectReq = await req.getData(objectReqOpts);
if(!objectReq.ok || !objectReq.res){
console.log('[ERROR] Objects Request FAILED!');
if(objectReq.error && objectReq.error.res && objectReq.error.res.body){
const objectInfo = JSON.parse(objectReq.error.res.body);
const objectInfo = JSON.parse(objectReq.error.res.body as string);
console.log('[INFO] Body:', JSON.stringify(objectInfo, null, '\t'));
objectInfo.error = true;
return objectInfo;
@ -741,8 +751,7 @@ async function getObjectById(returnData){
return { error: true };
}
const objectInfo = JSON.parse(objectReq.res.body);
const objectInfo = JSON.parse(objectReq.res.body) as ObjectInfo;
if(returnData){
return objectInfo;
}
@ -754,7 +763,7 @@ async function getObjectById(returnData){
await parseObject(item, 2, true, false);
continue;
}
const epMeta = {};
const epMeta: Partial<CrunchyEpMeta> = {};
switch (item.type) {
case 'episode':
item.s_num = 'S:' + item.episode_metadata.season_id;
@ -764,9 +773,9 @@ async function getObjectById(returnData){
epMeta.episodeTitle = item.title;
break;
case 'movie':
item.f_num = 'F:' + item.movie_metadata.movie_listing_id;
item.f_num = 'F:' + item.movie_metadata?.movie_listing_id;
epMeta.mediaId = 'M:'+ item.id;
epMeta.seasonTitle = item.movie_metadata.movie_listing_title;
epMeta.seasonTitle = item.movie_metadata?.movie_listing_title;
epMeta.episodeNumber = 'Movie';
epMeta.episodeTitle = item.title;
break;
@ -780,17 +789,17 @@ async function getObjectById(returnData){
}
if(selectedMedia.length > 1){
argv.appstore.isBatch = true;
appstore.isBatch = true;
}
console.log();
for(let media of selectedMedia){
await getMedia(media);
await getMedia(media as CrunchyEpMeta);
}
}
async function getMedia(mMeta){
async function getMedia(mMeta: CrunchyEpMeta){
let mediaName = '...';
if(mMeta.seasonTitle && mMeta.episodeNumber && mMeta.episodeTitle){
@ -804,32 +813,31 @@ async function getMedia(mMeta){
return;
}
if(appPatch.active){
mMeta = appPatch.doMod1(mMeta, argv);
}
let playbackReq = await req.getData(mMeta.playback);
let playbackReq = await req.getData(mMeta.playback, {useProxy: true});
if(!playbackReq.ok){
if(!playbackReq.ok || !playbackReq.res){
console.log('[ERROR] Request Stream URLs FAILED!');
return;
}
let pbData = JSON.parse(playbackReq.res.body);
let epNum = mMeta.episodeNumber;
if(epNum != '' && epNum !== null){
epNum = epNum.match(/^\d+$/) ? epNum.padStart(argv['episode-number-length'], '0') : epNum;
}
argv.appstore.fn = {};
argv.appstore.fn.title = argv.title ? argv.title : mMeta.seasonTitle,
argv.appstore.fn.epnum = !argv.appstore.isBatch && argv.episode ? argv.episode : epNum;
argv.appstore.fn.epttl = mMeta.episodeTitle;
argv.appstore.fn.out = fnOutputGen();
let pbData = JSON.parse(playbackReq.res.body) as PlaybackData;
appstore.fn = ([
['title', mMeta.episodeTitle],
['episode', mMeta.episodeNumber],
['service', 'Crunchyroll'],
['showTitle', mMeta.seasonTitle],
['season', mMeta.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 = [];
let hsLangs: string[] = [];
let pbStreams = pbData.streams;
for(let s of Object.keys(pbStreams)){
@ -838,8 +846,8 @@ async function getMedia(mMeta){
v.hardsub_lang = v.hardsub_locale
? langsData.fixAndFindCrLC(v.hardsub_locale).locale
: v.hardsub_locale;
if(s.hardsub_lang && hsLangs.indexOf(s.hardsub_lang) < 0){
hsLangs.push(s.hardsub_lang);
if(v.hardsub_lang && hsLangs.indexOf(v.hardsub_lang) < 0){
hsLangs.push(v.hardsub_lang);
}
return {
...v,
@ -1247,17 +1255,4 @@ function doCleanUp(isMuxed, muxFile, addSubs, sxList){
}
// done
console.log('\n[INFO] Done!\n');
}
function fnOutputGen(){
if(typeof argv.appstore.fn != 'object'){
argv.appstore.fn = {};
}
const fnPrepOutput = argv.filename.toString()
.replace('{rel_group}', argv['group-tag'])
.replace('{title}', argv.appstore.fn.title)
.replace('{ep_num}', argv.appstore.fn.epnum)
.replace('{ep_titl}', argv.appstore.fn.epttl)
.replace('{suffix}', argv.suffix.replace('SIZEp', argv.quality));
return shlp.cleanupFilename(fnPrepOutput);
}
}

View file

@ -14,7 +14,9 @@ const groups = {
'util': 'Utilities:'
}
const availableFilenameVars = [
export type AvailableFilenameVars = 'title' | 'episode' | 'showTitle' | 'season' | 'width' | 'height' | 'service'
const availableFilenameVars: AvailableFilenameVars[] = [
'title',
'episode',
'showTitle',
@ -110,6 +112,7 @@ const appArgv = (cfg: {
})
.option('e', {
group: groups.dl,
alias: 'episode',
describe: 'Sets the Episode Number/IDs (comma-separated, hyphen-sequence)',
type: 'string',
})

View file

@ -0,0 +1,40 @@
import * as shlp from "sei-helper";
import path from "path";
import { AvailableFilenameVars } from "./module.app-args";
export type Variable = ({
type: 'number',
replaceWith: number
} | {
}) & {
name: AvailableFilenameVars
}
const parseFileName = (input: string, variables: Variable[], numbers: number): string[] => {
const varRegex = /\${[A-Za-z1-9]+}/g;
const vars = input.match(varRegex);
if (!vars)
return [input];
for (let i = 0; i < vars.length; i++) {
const type = vars[i];
const varName = type.slice(2, -1).toLowerCase();
const use = variables.find(a => a.name === varName);
if (use === undefined) {
console.log(`[ERROR] Found variable '${type}' in fileName but no values was internally found!`)
continue;
}
if (use.type === 'number') {
const len = use.replaceWith.toFixed(0).length;
const replaceStr = len < numbers ? '0'.repeat(numbers - len) + use.replaceWith : use.replaceWith.toFixed(0);
input = input.replace(type, replaceStr);
} else {
input = input.replace(type, use.replaceWith)
}
}
return input.split(path.sep).map(a => shlp.cleanupFilename(a));
}
export default parseFileName;

View file

@ -117,7 +117,7 @@ const sortSubtitles = (data: Partial<LanguageItem>[], sortkey: keyof LanguageIte
const sortTags = (data: string[]) => {
const retData = data.map(e => { return { locale: e }; });
const sort = sortSubtitles(retData);
return sort.map(e => e.locale);
return sort.map(e => e.locale as string);
};
const subsFile = (fnOutput:string, subsIndex: string, langItem: LanguageItem) => {