From 146c5f10a99b45c91bd396603097acd023c46358 Mon Sep 17 00:00:00 2001 From: AnimeDL Date: Mon, 7 Oct 2024 20:53:23 -0700 Subject: [PATCH] Allow --syncTiming to work on all files Forgive me father for I have sinned (this code with it's "temporary" solutions). This maps all downloaded "versions" so that each version can have it's delay applied as needed --- @types/downloaderTypes.d.ts | 7 ++++- adn.ts | 31 ++++++++++++++++++-- ao.ts | 41 ++++++++++++++++++++++++-- crunchy.ts | 58 +++++++++++++++++++++++++++++++++++-- hidive.ts | 35 +++++++++++++++++++--- modules/module.merger.ts | 31 ++++++++++++++++++++ 6 files changed, 189 insertions(+), 14 deletions(-) diff --git a/@types/downloaderTypes.d.ts b/@types/downloaderTypes.d.ts index ec8d4d3..878438d 100644 --- a/@types/downloaderTypes.d.ts +++ b/@types/downloaderTypes.d.ts @@ -29,4 +29,9 @@ export type DownloadedMedia = { type: 'Subtitle', signs?: boolean, cc: boolean -} & sxItem ) \ No newline at end of file +} & sxItem ) + +export type DownloadedMediaMap = { + version: string; + files: DownloadedMedia[]; +} \ No newline at end of file diff --git a/adn.ts b/adn.ts index 7e5c087..0ddc640 100644 --- a/adn.ts +++ b/adn.ts @@ -28,7 +28,7 @@ import { AvailableFilenameVars } from './modules/module.args'; // Types import type { ServiceClass } from './@types/serviceClassInterface'; import type { AuthData, AuthResponse, SearchData, SearchResponse, SearchResponseItem } from './@types/messageHandler'; -import type { DownloadedMedia, sxItem } from './@types/downloaderTypes'; +import type { DownloadedMedia, DownloadedMediaMap, sxItem } from './@types/downloaderTypes'; import type { ADNSearch, ADNSearchShow } from './@types/adnSearch'; import type { ADNVideo, ADNVideos } from './@types/adnVideos'; import type { ADNPlayerConfig } from './@types/adnPlayerConfig'; @@ -315,7 +315,7 @@ export default class AnimationDigitalNetwork implements ServiceClass { return { isOk: true, value: selEpsArr }; } - public async muxStreams(data: DownloadedMedia[], options: yargs.ArgvType) { + public async muxStreams(data: DownloadedMedia[], mediaMap: DownloadedMediaMap[], options: yargs.ArgvType) { this.cfg.bin = await yamlCfg.loadBinCfg(); let hasAudioStreams = false; if (options.novids || data.filter(a => a.type === 'Video').length === 0) @@ -324,6 +324,7 @@ export default class AnimationDigitalNetwork implements ServiceClass { hasAudioStreams = true; } const merger = new Merger({ + mediaMap, onlyVid: hasAudioStreams ? data.filter(a => a.type === 'Video').map((a) : MergerInput => { return { lang: a.lang, @@ -404,7 +405,7 @@ export default class AnimationDigitalNetwork implements ServiceClass { return { isOk: false, reason: new Error('Failed to download media list') }; } else { if (!options.skipmux) { - await this.muxStreams(res.data, { ...options, output: res.fileName }); + await this.muxStreams(res.data, res.mediaMap, { ...options, output: res.fileName }); } else { console.info('Skipping mux'); } @@ -433,6 +434,12 @@ export default class AnimationDigitalNetwork implements ServiceClass { } const files: DownloadedMedia[] = []; + const mediaMap: DownloadedMediaMap[] = []; + + const fileMap: DownloadedMediaMap = { + version: data.id.toString(), + files: [] + }; let dlFailed = false; let dlVideoOnce = false; // Variable to save if best selected video quality was downloaded @@ -697,6 +704,11 @@ export default class AnimationDigitalNetwork implements ServiceClass { path: `${tsFile}.ts`, lang: audDub }); + fileMap.files.push({ + type: 'Video', + path: `${tsFile}.ts`, + lang: audDub + }); dlVideoOnce = true; } } else{ @@ -763,6 +775,11 @@ export default class AnimationDigitalNetwork implements ServiceClass { lang: langsData.languages.find(a=>a.code=='jpn')!, type: 'Chapters' }); + fileMap.files.push({ + path: `${tsFile}.txt`, + lang: langsData.languages.find(a=>a.code=='jpn')!, + type: 'Chapters' + }); } catch { console.error('Failed to write chapter file'); } @@ -886,6 +903,11 @@ export default class AnimationDigitalNetwork implements ServiceClass { ...sxData as sxItem, cc: false }); + fileMap.files.push({ + type: 'Subtitle', + ...sxData as sxItem, + cc: false + }); } subIndex++; } @@ -896,9 +918,12 @@ export default class AnimationDigitalNetwork implements ServiceClass { console.info('Subtitles downloading skipped!'); } + mediaMap.push(fileMap); + return { error: dlFailed, data: files, + mediaMap, fileName: fileName ? (path.isAbsolute(fileName) ? fileName : path.join(this.cfg.dir.content, fileName)) || './unknown' : './unknown' }; } diff --git a/ao.ts b/ao.ts index 5ada8b3..0f105ca 100644 --- a/ao.ts +++ b/ao.ts @@ -32,7 +32,7 @@ import type { AuthData, AuthResponse, SearchData, SearchResponse, SearchResponse import type { AOSearchResult, AnimeOnegaiSearch } from './@types/animeOnegaiSearch'; import type { AnimeOnegaiSeries } from './@types/animeOnegaiSeries'; import type { AnimeOnegaiSeasons, Episode } from './@types/animeOnegaiSeasons'; -import type { DownloadedMedia, sxItem } from './@types/downloaderTypes'; +import type { DownloadedMedia, DownloadedMediaMap, sxItem } from './@types/downloaderTypes'; import type { AnimeOnegaiStream } from './@types/animeOnegaiStream'; type parsedMultiDubDownload = { @@ -314,7 +314,7 @@ export default class AnimeOnegai implements ServiceClass { return false; } else { if (!options.skipmux) { - await this.muxStreams(res.data, { ...options, output: res.fileName }); + await this.muxStreams(res.data, res.mediaMap, { ...options, output: res.fileName }); } else { console.info('Skipping mux'); } @@ -326,7 +326,7 @@ export default class AnimeOnegai implements ServiceClass { return true; } - public async muxStreams(data: DownloadedMedia[], options: yargs.ArgvType) { + public async muxStreams(data: DownloadedMedia[], mediaMap: DownloadedMediaMap[], options: yargs.ArgvType) { this.cfg.bin = await yamlCfg.loadBinCfg(); let hasAudioStreams = false; if (options.novids || data.filter(a => a.type === 'Video').length === 0) @@ -335,6 +335,7 @@ export default class AnimeOnegai implements ServiceClass { hasAudioStreams = true; } const merger = new Merger({ + mediaMap, onlyVid: hasAudioStreams ? data.filter(a => a.type === 'Video').map((a) : MergerInput => { return { lang: a.lang, @@ -402,6 +403,7 @@ export default class AnimeOnegai implements ServiceClass { public async downloadMediaList(medias: parsedMultiDubDownload, options: yargs.ArgvType) : Promise<{ data: DownloadedMedia[], + mediaMap: DownloadedMediaMap[], fileName: string, error: boolean } | undefined> { @@ -421,6 +423,7 @@ export default class AnimeOnegai implements ServiceClass { } const files: DownloadedMedia[] = []; + const mediaMap: DownloadedMediaMap[] = []; let subIndex = 0; let dlFailed = false; @@ -428,6 +431,11 @@ export default class AnimeOnegai implements ServiceClass { for (const media of medias.data) { console.info(`Requesting: [E.${media.episode.ID}] ${mediaName}`); + + const fileMap: DownloadedMediaMap = { + version: media.episode.ID.toString(), + files: [] + }; const AuthHeaders = { headers: { @@ -700,6 +708,11 @@ export default class AnimeOnegai implements ServiceClass { path: `${tsFile}.video.mp4`, lang: lang }); + fileMap.files.push({ + type: 'Video', + path: `${tsFile}.video.mp4`, + lang: lang + }); } } @@ -721,6 +734,11 @@ export default class AnimeOnegai implements ServiceClass { path: `${tsFile}.audio.mp4`, lang: lang }); + fileMap.files.push({ + type: 'Audio', + path: `${tsFile}.audio.mp4`, + lang: lang + }); console.info('Decryption done for audio'); } } @@ -734,6 +752,11 @@ export default class AnimeOnegai implements ServiceClass { path: `${tsFile}.video.mp4`, lang: lang }); + fileMap.files.push({ + type: 'Video', + path: `${tsFile}.video.mp4`, + lang: lang + }); } if (audioDownloaded) { files.push({ @@ -741,6 +764,11 @@ export default class AnimeOnegai implements ServiceClass { path: `${tsFile}.audio.mp4`, lang: lang }); + fileMap.files.push({ + type: 'Audio', + path: `${tsFile}.audio.mp4`, + lang: lang + }); } } } @@ -782,6 +810,11 @@ export default class AnimeOnegai implements ServiceClass { ...sxData as sxItem, cc: false }); + fileMap.files.push({ + type: 'Subtitle', + ...sxData as sxItem, + cc: false + }); } else{ console.warn(`Failed to download subtitle: ${sxData.file}`); } @@ -795,11 +828,13 @@ export default class AnimeOnegai implements ServiceClass { else{ console.info('Subtitles downloading skipped!'); } + mediaMap.push(fileMap); await this.sleep(options.waittime); } return { error: dlFailed, data: files, + mediaMap, fileName: fileName ? (path.isAbsolute(fileName) ? fileName : path.join(this.cfg.dir.content, fileName)) || './unknown' : './unknown' }; } diff --git a/crunchy.ts b/crunchy.ts index 9772ccf..350fa74 100644 --- a/crunchy.ts +++ b/crunchy.ts @@ -32,7 +32,7 @@ import { exec } from './modules/sei-helper-fixes'; // Types import type { CrunchyDownloadOptions, CrunchyEpMeta, CrunchyMuxOptions, CrunchyMultiDownload, ParseItem, SeriesSearch, SeriesSearchItem } from './@types/crunchyTypes'; -import type { DownloadedMedia, sxItem } from './@types/downloaderTypes'; +import type { DownloadedMedia, DownloadedMediaMap, sxItem } from './@types/downloaderTypes'; import type { CrunchySearch } from './@types/crunchySearch'; import type { CrunchyEpisodeList, CrunchyEpisode } from './@types/crunchyEpisodeList'; import type { ObjectInfo } from './@types/objectInfo'; @@ -976,7 +976,7 @@ export default class Crunchy implements ServiceClass { return false; } else { if (!options.skipmux) { - await this.muxStreams(res.data, { ...options, output: res.fileName }); + await this.muxStreams(res.data, res.mediaMap, { ...options, output: res.fileName }); } else { console.info('Skipping mux'); } @@ -1186,6 +1186,7 @@ export default class Crunchy implements ServiceClass { public async downloadMediaList(medias: CrunchyEpMeta, options: CrunchyDownloadOptions) : Promise<{ data: DownloadedMedia[], + mediaMap: DownloadedMediaMap[], fileName: string, error: boolean } | undefined> { @@ -1205,6 +1206,7 @@ export default class Crunchy implements ServiceClass { } const files: DownloadedMedia[] = []; + const mediaMap: DownloadedMediaMap[] = []; if(medias.data.every(a => !a.playback)){ console.warn('Video not available!'); @@ -1221,6 +1223,11 @@ export default class Crunchy implements ServiceClass { // Make sure we have a media id without a : in it const currentMediaId = (mMeta.mediaId.includes(':') ? mMeta.mediaId.split(':')[1] : mMeta.mediaId); + const fileMap: DownloadedMediaMap = { + version: currentMediaId, + files: [] + }; + //Make sure token is up-to-date await this.refreshToken(true, true); let currentVersion; @@ -1770,6 +1777,12 @@ export default class Crunchy implements ServiceClass { lang: lang, isPrimary: isPrimary }); + fileMap.files.push({ + type: 'Video', + path: `${tsFile}.video.m4s`, + lang: lang, + isPrimary: isPrimary + }); } } @@ -1792,6 +1805,12 @@ export default class Crunchy implements ServiceClass { lang: lang, isPrimary: isPrimary }); + fileMap.files.push({ + type: 'Audio', + path: `${tsFile}.audio.m4s`, + lang: lang, + isPrimary: isPrimary + }); console.info('Decryption done for audio'); } } @@ -1806,6 +1825,12 @@ export default class Crunchy implements ServiceClass { lang: lang, isPrimary: isPrimary }); + fileMap.files.push({ + type: 'Video', + path: `${tsFile}.video.m4s`, + lang: lang, + isPrimary: isPrimary + }); } if (audioDownloaded) { files.push({ @@ -1814,6 +1839,12 @@ export default class Crunchy implements ServiceClass { lang: lang, isPrimary: isPrimary }); + fileMap.files.push({ + type: 'Audio', + path: `${tsFile}.audio.m4s`, + lang: lang, + isPrimary: isPrimary + }); } } } else if (!options.novids) { @@ -1971,6 +2002,12 @@ export default class Crunchy implements ServiceClass { lang: lang, isPrimary: isPrimary }); + fileMap.files.push({ + type: 'Video', + path: `${tsFile}.ts`, + lang: lang, + isPrimary: isPrimary + }); dlVideoOnce = true; } } else{ @@ -2008,6 +2045,11 @@ export default class Crunchy implements ServiceClass { lang: lang, type: 'Chapters' }); + fileMap.files.push({ + path: `${tsFile}.txt`, + lang: lang, + type: 'Chapters' + }); } catch { console.error('Failed to write chapter file'); } @@ -2095,6 +2137,12 @@ export default class Crunchy implements ServiceClass { cc: isCC, signs: isSigns, }); + fileMap.files.push({ + type: 'Subtitle', + ...sxData as sxItem, + cc: isCC, + signs: isSigns, + }); } else{ console.warn(`Failed to download subtitle: ${sxData.file}`); @@ -2109,16 +2157,19 @@ export default class Crunchy implements ServiceClass { console.info('Subtitles downloading skipped!'); } + mediaMap.push(fileMap); await this.sleep(options.waittime); } + return { error: dlFailed, data: files, + mediaMap, fileName: fileName ? (path.isAbsolute(fileName) ? fileName : path.join(this.cfg.dir.content, fileName)) || './unknown' : './unknown' }; } - public async muxStreams(data: DownloadedMedia[], options: CrunchyMuxOptions) { + public async muxStreams(data: DownloadedMedia[], mediaMap: DownloadedMediaMap[], options: CrunchyMuxOptions) { this.cfg.bin = await yamlCfg.loadBinCfg(); let hasAudioStreams = false; if (options.novids || data.filter(a => a.type === 'Video').length === 0) @@ -2127,6 +2178,7 @@ export default class Crunchy implements ServiceClass { hasAudioStreams = true; } const merger = new Merger({ + mediaMap, onlyVid: hasAudioStreams ? data.filter(a => a.type === 'Video').map((a) : MergerInput => { return { lang: a.lang, diff --git a/hidive.ts b/hidive.ts index 06f9403..d5067f4 100644 --- a/hidive.ts +++ b/hidive.ts @@ -36,7 +36,7 @@ import type { NewHidiveSeries } from './@types/newHidiveSeries'; import type { Episode, NewHidiveEpisodeExtra, NewHidiveSeason, NewHidiveSeriesExtra } from './@types/newHidiveSeason'; import type { NewHidiveEpisode } from './@types/newHidiveEpisode'; import type { NewHidivePlayback, Subtitle } from './@types/newHidivePlayback'; -import type { DownloadedMedia, sxItem } from './@types/downloaderTypes'; +import type { DownloadedMedia, DownloadedMediaMap, sxItem } from './@types/downloaderTypes'; export default class Hidive implements ServiceClass { public cfg: yamlCfg.ConfigObject; @@ -546,7 +546,7 @@ export default class Hidive implements ServiceClass { return { isOk: false, reason: new Error('Failed to download media list') }; } else { if (!options.skipmux) { - await this.muxStreams(res.data, { ...options, output: res.fileName }, false); + await this.muxStreams(res.data, res.mediaMap, { ...options, output: res.fileName }, false); } else { console.info('Skipping mux'); } @@ -635,7 +635,7 @@ export default class Hidive implements ServiceClass { return { isOk: false, reason: new Error('Failed to download media list') }; } else { if (!options.skipmux) { - await this.muxStreams(res.data, { ...options, output: res.fileName }, false); + await this.muxStreams(res.data, res.mediaMap, { ...options, output: res.fileName }, false); } else { console.info('Skipping mux'); } @@ -650,6 +650,7 @@ export default class Hidive implements ServiceClass { public async downloadMPD(streamPlaylists: MPDParsed, subs: Subtitle[], selectedEpisode: NewHidiveEpisodeExtra, options: Record) { //let fileName: string; const files: DownloadedMedia[] = []; + const mediaMap: DownloadedMediaMap[] = []; const variables: Variable[] = []; let dlFailed = false; const subsMargin = 0; @@ -657,6 +658,11 @@ export default class Hidive implements ServiceClass { let encryptionKeys: KeyContainer[] = []; if (!canDecrypt) console.warn('Decryption not enabled!'); + const fileMap: DownloadedMediaMap = { + version: selectedEpisode.id.toString(), + files: [] + }; + if (!this.cfg.bin.ffmpeg) this.cfg.bin = await yamlCfg.loadBinCfg(); @@ -838,6 +844,12 @@ export default class Hidive implements ServiceClass { lang: chosenAudios[0].language, isPrimary: true }); + fileMap.files.push({ + type: 'Video', + path: `${tsFile}.video.m4s`, + lang: chosenAudios[0].language, + isPrimary: true + }); } } else { console.warn('mp4decrypt not found, files need decryption. Decryption Keys:', encryptionKeys); @@ -919,6 +931,12 @@ export default class Hidive implements ServiceClass { lang: chosenAudioSegments.language, isPrimary: chosenAudioSegments.default }); + fileMap.files.push({ + type: 'Audio', + path: `${tsFile}.audio.m4s`, + lang: chosenAudioSegments.language, + isPrimary: chosenAudioSegments.default + }); console.info('Decryption done for audio'); } } else { @@ -967,6 +985,11 @@ export default class Hidive implements ServiceClass { ...sxData as sxItem, cc: false }); + fileMap.files.push({ + type: 'Subtitle', + ...sxData as sxItem, + cc: false + }); } else{ console.warn(`Failed to download subtitle: ${sxData.file}`); } @@ -980,14 +1003,17 @@ export default class Hidive implements ServiceClass { console.info('Subtitles downloading skipped!'); } + mediaMap.push(fileMap); + return { error: dlFailed, data: files, + mediaMap, fileName: fileName ? (path.isAbsolute(fileName) ? fileName : path.join(this.cfg.dir.content, fileName)) || './unknown' : './unknown' }; } - public async muxStreams(data: DownloadedMedia[], options: Record, inverseTrackOrder: boolean = true) { + public async muxStreams(data: DownloadedMedia[], mediaMap: DownloadedMediaMap[], options: Record, inverseTrackOrder: boolean = true) { this.cfg.bin = await yamlCfg.loadBinCfg(); let hasAudioStreams = false; if (options.novids || data.filter(a => a.type === 'Video').length === 0) @@ -996,6 +1022,7 @@ export default class Hidive implements ServiceClass { hasAudioStreams = true; } const merger = new Merger({ + mediaMap, onlyVid: hasAudioStreams ? data.filter(a => a.type === 'Video').map((a) : MergerInput => { return { lang: a.lang, diff --git a/modules/module.merger.ts b/modules/module.merger.ts index c3a78a2..82e63e2 100644 --- a/modules/module.merger.ts +++ b/modules/module.merger.ts @@ -11,6 +11,7 @@ import ffprobe from 'ffprobe'; import {spawn} from 'node:child_process'; import {OggOpusDecodedAudio, OggOpusDecoder} from 'ogg-opus-decoder'; import SynAudio, {MultipleClipMatch, MultipleClipMatchFirst} from 'synaudio'; +import { DownloadedMediaMap } from '../@types/downloaderTypes'; export type MergerInput = { path: string, @@ -38,6 +39,7 @@ export type ParsedFont = { } export type MergerOptions = { + mediaMap?: DownloadedMediaMap[], videoAndAudio: MergerInput[], onlyVid: MergerInput[], onlyAudio: MergerInput[], @@ -115,6 +117,9 @@ class Merger { public async createDelays() { const bin = await yamlCfg.loadBinCfg(); + const allFiles = [...this.options.onlyAudio, ...this.options.videoAndAudio, ...this.options.onlyVid, ...this.options.subtitles]; + if (this.options.chapters) + allFiles.push(...this.options.chapters); const audios = [...this.options.onlyAudio, ...this.options.videoAndAudio]; if (audios.length < 2) return; @@ -167,9 +172,35 @@ class Merger { sampleOffset: maxSampleOffset - item.sampleOffset })); + //Iterate over found matches invertedArray.forEach((clip: MultipleClipMatch | MultipleClipMatchFirst) => { const audio = audios.find(a => a.path === clip.name)!; audio.delay = (clip.sampleOffset || 0 ) / audio.duration!.sampleRate; + + // Iterate over each DownloadedMediaMap in the array + for (const mediaMap of this.options.mediaMap!) { + // Iterate over the files array in each DownloadedMediaMap + for (const media of mediaMap.files) { + // Check if the path matches + if (media.path === clip.name) { + console.log('Matching path found:', media.path); + + // Re-iterate over the files array where the match was found + mediaMap.files.forEach((file) => { + const mergerFile = allFiles.find(a => + ('path' in a ? a.path : a.file) === file.path + ); + if (mergerFile) + mergerFile.delay = audio.delay; + else + console.error(`File ${file.path} was not found in allFiles array, this shouldn't happen`); + }); + + // We only want the first match, so break out of the loop + break; + } + } + } }); }