mirror of
https://github.com/anidl/multi-downloader-nx.git
synced 2026-03-11 17:45:30 +00:00
[CR] Initial commit to support chapters
TODO: 1) Add flag for chapters 2) Add ffmpeg merging for chapters 3) Add fallback to old CR API
This commit is contained in:
parent
7be22ec132
commit
02620ec5b5
4 changed files with 121 additions and 2 deletions
16
@types/crunchyChapters.d.ts
vendored
Normal file
16
@types/crunchyChapters.d.ts
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
export interface CrunchyChapters {
|
||||
[key: string]: CrunchyChapter;
|
||||
lastUpdate: Date;
|
||||
mediaId: string;
|
||||
}
|
||||
|
||||
export interface CrunchyChapter {
|
||||
approverId: string;
|
||||
distributionNumber: string;
|
||||
end: number;
|
||||
start: number;
|
||||
title: string;
|
||||
seriesId: string;
|
||||
new: boolean;
|
||||
type: string;
|
||||
}
|
||||
4
@types/crunchyTypes.d.ts
vendored
4
@types/crunchyTypes.d.ts
vendored
|
|
@ -90,6 +90,10 @@ export type DownloadedMedia = {
|
|||
lang: LanguageItem,
|
||||
path: string,
|
||||
isPrimary?: boolean
|
||||
} | {
|
||||
type: 'Chapters',
|
||||
lang: LanguageItem,
|
||||
path: string
|
||||
} | ({
|
||||
type: 'Subtitle',
|
||||
cc: boolean
|
||||
|
|
|
|||
91
crunchy.ts
91
crunchy.ts
|
|
@ -40,6 +40,7 @@ import { CrunchyAndroidStreams } from './@types/crunchyAndroidStreams';
|
|||
import { CrunchyAndroidEpisodes } from './@types/crunchyAndroidEpisodes';
|
||||
import { parse } from './modules/module.transform-mpd';
|
||||
import { CrunchyAndroidObject } from './@types/crunchyAndroidObject';
|
||||
import { CrunchyChapters, CrunchyChapter } from './@types/crunchyChapters';
|
||||
|
||||
export type sxItem = {
|
||||
language: langsData.LanguageItem,
|
||||
|
|
@ -1174,7 +1175,55 @@ export default class Crunchy implements ServiceClass {
|
|||
if (mediaId.includes(':'))
|
||||
mediaId = mediaId.split(':')[1];
|
||||
|
||||
// /cms/v2/BUCKET/crunchyroll/videos/MEDIAID/streams
|
||||
const chapterRequest = await this.req.getData(`https://static.crunchyroll.com/skip-events/production/${mMeta.mediaId}.json`);
|
||||
let chapterData: CrunchyChapters;
|
||||
const compiledChapters: string[] = [];
|
||||
if(!chapterRequest.ok || !chapterRequest.res){
|
||||
console.warn('Chapter request failed');
|
||||
} else {
|
||||
chapterData = JSON.parse(chapterRequest.res.body);
|
||||
const chapters: CrunchyChapter[] = [];
|
||||
for (const chapter in chapterData) {
|
||||
if (typeof chapterData[chapter] == 'object') {
|
||||
chapters.push(chapterData[chapter]);
|
||||
}
|
||||
}
|
||||
if (chapters.length > 0) {
|
||||
chapters.sort((a, b) => a.start - b.start);
|
||||
for (const chapter of chapters) {
|
||||
const startTime = new Date(0), endTime = new Date(0);
|
||||
startTime.setSeconds(chapter.start);
|
||||
endTime.setSeconds(chapter.end);
|
||||
const startFormatted = startTime.toISOString().substring(11, 19)+'.00';
|
||||
const endFormatted = endTime.toISOString().substring(11, 19)+'.00';
|
||||
if (chapter.type == 'intro') {
|
||||
if (chapter.start > 0) {
|
||||
compiledChapters.push(
|
||||
`CHAPTER${(compiledChapters.length/2)+1}=00:00:00.00`,
|
||||
`CHAPTER${(compiledChapters.length/2)+1}NAME=Prologue`
|
||||
);
|
||||
}
|
||||
compiledChapters.push(
|
||||
`CHAPTER${(compiledChapters.length/2)+1}=${startFormatted}`,
|
||||
`CHAPTER${(compiledChapters.length/2)+1}NAME=Opening`
|
||||
);
|
||||
compiledChapters.push(
|
||||
`CHAPTER${(compiledChapters.length/2)+1}=${endFormatted}`,
|
||||
`CHAPTER${(compiledChapters.length/2)+1}NAME=Episode`
|
||||
);
|
||||
} else {
|
||||
compiledChapters.push(
|
||||
`CHAPTER${(compiledChapters.length/2)+1}=${startFormatted}`,
|
||||
`CHAPTER${(compiledChapters.length/2)+1}NAME=${chapter.type.charAt(0).toUpperCase() + chapter.type.slice(1)} Start`
|
||||
);
|
||||
compiledChapters.push(
|
||||
`CHAPTER${(compiledChapters.length/2)+1}=${endFormatted}`,
|
||||
`CHAPTER${(compiledChapters.length/2)+1}NAME=${chapter.type.charAt(0).toUpperCase() + chapter.type.slice(1)} End`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let pbData = { total: 0, data: {}, meta: {} } as PlaybackData;
|
||||
if (options.apiType == 'android') {
|
||||
|
|
@ -1797,6 +1846,32 @@ export default class Crunchy implements ServiceClass {
|
|||
console.info('Downloading skipped!');
|
||||
}
|
||||
|
||||
if (compiledChapters.length > 0) {
|
||||
try {
|
||||
fileName = parseFileName(options.fileName, variables, options.numbers, options.override).join(path.sep);
|
||||
const outFile = parseFileName(options.fileName + '.' + mMeta.lang?.name, variables, options.numbers, options.override).join(path.sep);
|
||||
tsFile = path.isAbsolute(outFile as string) ? outFile : path.join(this.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 ? '' : this.cfg.dir.content, ...arr.slice(0, ind), val)))
|
||||
fs.mkdirSync(path.join(isAbsolut ? '' : this.cfg.dir.content, ...arr.slice(0, ind), val));
|
||||
});
|
||||
const lang = langsData.languages.find(a => a.code === curStream?.audio_lang);
|
||||
if (!lang) {
|
||||
console.error(`Unable to find language for code ${curStream.audio_lang}`);
|
||||
return;
|
||||
}
|
||||
fs.writeFileSync(`${tsFile}.txt`, compiledChapters.join('\r\n'));
|
||||
files.push({
|
||||
path: `${tsFile}.txt`,
|
||||
lang: lang,
|
||||
type: 'Chapters'
|
||||
});
|
||||
} catch {
|
||||
console.error('Failed to write chapter file');
|
||||
}
|
||||
}
|
||||
|
||||
if(options.dlsubs.indexOf('all') > -1){
|
||||
options.dlsubs = ['all'];
|
||||
|
|
@ -1912,6 +1987,8 @@ export default class Crunchy implements ServiceClass {
|
|||
throw new Error('Never');
|
||||
if (a.type === 'Audio')
|
||||
throw new Error('Never');
|
||||
if (a.type === 'Chapters')
|
||||
throw new Error('Never');
|
||||
return {
|
||||
file: a.path,
|
||||
language: a.language,
|
||||
|
|
@ -1929,6 +2006,18 @@ export default class Crunchy implements ServiceClass {
|
|||
path: a.path,
|
||||
};
|
||||
}),
|
||||
chapters: data.filter(a => a.type === 'Chapters').map((a) : MergerInput => {
|
||||
if (a.type === 'Video')
|
||||
throw new Error('Never');
|
||||
if (a.type === 'Audio')
|
||||
throw new Error('Never');
|
||||
if (a.type === 'Subtitle')
|
||||
throw new Error('Never');
|
||||
return {
|
||||
path: a.path,
|
||||
lang: a.lang
|
||||
};
|
||||
}),
|
||||
videoTitle: options.videoTitle,
|
||||
options: {
|
||||
ffmpeg: options.ffmpegOptions,
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ export type MergerOptions = {
|
|||
onlyVid: MergerInput[],
|
||||
onlyAudio: MergerInput[],
|
||||
subtitles: SubtitleInput[],
|
||||
chapters?: MergerInput[],
|
||||
ccTag: string,
|
||||
output: string,
|
||||
videoTitle?: string,
|
||||
|
|
@ -162,7 +163,7 @@ class Merger {
|
|||
args.push(`-i "${sub.file}"`);
|
||||
}
|
||||
|
||||
if (this.options.output.split('.').pop() === 'mkv')
|
||||
if (this.options.output.split('.').pop() === 'mkv') {
|
||||
if (this.options.fonts) {
|
||||
let fontIndex = 0;
|
||||
for (const font of this.options.fonts) {
|
||||
|
|
@ -170,6 +171,9 @@ class Merger {
|
|||
fontIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: Make it possible for chapters to work with ffmpeg merging
|
||||
|
||||
args.push(...metaData);
|
||||
args.push(...this.options.subtitles.map((_, subIndex) => `-map ${subIndex + index}`));
|
||||
|
|
@ -296,6 +300,7 @@ class Merger {
|
|||
'--no-subtitles',
|
||||
);
|
||||
}
|
||||
|
||||
if (this.options.fonts && this.options.fonts.length > 0) {
|
||||
for (const f of this.options.fonts) {
|
||||
args.push('--attachment-name', f.name);
|
||||
|
|
@ -308,6 +313,10 @@ class Merger {
|
|||
);
|
||||
}
|
||||
|
||||
if (this.options.chapters && this.options.chapters.length > 0) {
|
||||
args.push(`--chapters "${this.options.chapters[0].path}"`);
|
||||
}
|
||||
|
||||
return args.join(' ');
|
||||
};
|
||||
|
||||
|
|
@ -405,6 +414,7 @@ class Merger {
|
|||
|
||||
public cleanUp() {
|
||||
this.options.onlyAudio.concat(this.options.onlyVid).concat(this.options.videoAndAudio).forEach(a => fs.unlinkSync(a.path));
|
||||
this.options.chapters?.forEach(a => fs.unlinkSync(a.path));
|
||||
this.options.subtitles.forEach(a => fs.unlinkSync(a.file));
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue