75%
This commit is contained in:
parent
58697e8b0f
commit
22b67b45d5
7 changed files with 224 additions and 54 deletions
|
|
@ -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
97
@types/objectInfo.d.ts
vendored
Normal 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
34
@types/playbackData.d.ts
vendored
Normal 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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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',
|
||||
})
|
||||
|
|
|
|||
40
modules/module.filename.ts
Normal file
40
modules/module.filename.ts
Normal 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;
|
||||
|
|
@ -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) => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue