From ec1f4e6ba95349377707a27bd6f9c3869e7aeba8 Mon Sep 17 00:00:00 2001 From: stratumadev Date: Thu, 4 Sep 2025 22:27:40 +0200 Subject: [PATCH] hotfix crunchyroll 192 audio + cbr video dl --- @types/crunchyTypes.d.ts | 8 ++++---- @types/enums.ts | 21 +++++---------------- crunchy.ts | 37 ++++++++++++++++++++++++++++++------- modules/module.api-urls.ts | 4 ++-- modules/module.app-args.ts | 8 ++++---- modules/module.args.ts | 10 +++++----- 6 files changed, 50 insertions(+), 38 deletions(-) diff --git a/@types/crunchyTypes.d.ts b/@types/crunchyTypes.d.ts index 5a64097..0aa8960 100644 --- a/@types/crunchyTypes.d.ts +++ b/@types/crunchyTypes.d.ts @@ -2,14 +2,14 @@ import { HLSCallback } from 'hls-download'; import { sxItem } from '../crunchy'; import { LanguageItem } from '../modules/module.langsData'; import { DownloadInfo } from './messageHandler'; -import { CrunchyPlayStreams } from './enums'; +import { CrunchyVideoPlayStreams, CrunchyAudioPlayStreams } from './enums'; export type CrunchyDownloadOptions = { hslang: string, // kstream: number, - cstream: keyof typeof CrunchyPlayStreams, - vstream: keyof typeof CrunchyPlayStreams, - astream: keyof typeof CrunchyPlayStreams, + cstream: keyof typeof CrunchyVideoPlayStreams, + vstream: keyof typeof CrunchyVideoPlayStreams, + astream: keyof typeof CrunchyAudioPlayStreams, tsd?: boolean, novids?: boolean, noaudio?: boolean, diff --git a/@types/enums.ts b/@types/enums.ts index c17ee27..cb81f69 100644 --- a/@types/enums.ts +++ b/@types/enums.ts @@ -1,19 +1,8 @@ -export enum CrunchyPlayStreams { - 'chrome' = 'web/chrome', - 'firefox' = 'web/firefox', - 'safari' = 'web/safari', - 'edge' = 'web/edge', - 'fallback' = 'web/fallback', - 'ps4' = 'console/ps4', - 'ps5' = 'console/ps5', - 'switch' = 'console/switch', - 'xboxone' = 'console/xbox_one', - 'vidaa' = 'tv/vidaa', - 'samsungtv' = 'tv/samsung', - 'lgtv' = 'tv/lg', - 'rokutv' = 'tv/roku', - 'chromecast'= 'tv/chromecast', - 'firetv' = 'tv/fire_tv', +export enum CrunchyVideoPlayStreams { + 'androidtv' = 'tv/android_tv' +} + +export enum CrunchyAudioPlayStreams { 'androidtv' = 'tv/android_tv', 'android' = 'android/phone', 'androidtab'= 'android/tablet' diff --git a/crunchy.ts b/crunchy.ts index 1f7b43c..7f338e6 100644 --- a/crunchy.ts +++ b/crunchy.ts @@ -43,7 +43,7 @@ import { CrunchyAndroidObject } from './@types/crunchyAndroidObject'; import { CrunchyChapters, CrunchyChapter, CrunchyOldChapter } from './@types/crunchyChapters'; import vtt2ass from './modules/module.vtt2ass'; import { CrunchyPlayStream } from './@types/crunchyPlayStreams'; -import { CrunchyPlayStreams } from './@types/enums'; +import { CrunchyVideoPlayStreams, CrunchyAudioPlayStreams } from './@types/enums'; import { randomUUID } from 'node:crypto'; export type sxItem = { @@ -1434,6 +1434,20 @@ export default class Crunchy implements ServiceClass { return selectedMedia; } + private convertDownloadToPlayback(audioUrl: string, videoUrl: string): string { + try { + const url = new URL(audioUrl); + const urla = new URL(videoUrl); + url.pathname = url.pathname.replace('/manifest/download/', '/manifest/'); + url.searchParams.delete('downloadGuid'); + url.searchParams.set('playbackGuid', urla.searchParams.get('playbackGuid') as string); + + return url.toString(); + } catch (err) { + return audioUrl; + } + } + public async downloadMediaList(medias: CrunchyEpMeta, options: CrunchyDownloadOptions) : Promise<{ data: DownloadedMedia[], fileName: string, @@ -1637,6 +1651,7 @@ export default class Crunchy implements ServiceClass { let videoStream: CrunchyPlayStream | null = null; let audioStream: CrunchyPlayStream | null = null; + const isDLBypass: boolean = options.astream === 'android' || options.astream === 'androidtab' ? true : false; if (options.tsd) { console.warn('Total Session Death Active'); @@ -1650,7 +1665,7 @@ export default class Crunchy implements ServiceClass { } } - const videoPlaybackReq = await this.req.getData(`https://www.crunchyroll.com/playback/v3/${currentVersion ? currentVersion.guid : currentMediaId}/${CrunchyPlayStreams[options.vstream]}/play`, AuthHeaders); + const videoPlaybackReq = await this.req.getData(`https://www.crunchyroll.com/playback/v3/${currentVersion ? currentVersion.guid : currentMediaId}/${CrunchyVideoPlayStreams[options.vstream]}/play`, AuthHeaders); if (!videoPlaybackReq.ok || !videoPlaybackReq.res) { console.warn('Request Video Stream URLs FAILED!'); } else { @@ -1681,8 +1696,8 @@ export default class Crunchy implements ServiceClass { }; } - if (!options.cstream && (options.vstream !== options.astream)) { - const audioPlaybackReq = await this.req.getData(`https://www.crunchyroll.com/playback/v3/${currentVersion ? currentVersion.guid : currentMediaId}/${CrunchyPlayStreams[options.astream]}/play`, AuthHeaders); + if (!options.cstream && (options.vstream !== options.astream) && videoStream) { + const audioPlaybackReq = await this.req.getData(`https://www.crunchyroll.com/playback/v3/${currentVersion ? currentVersion.guid : currentMediaId}/${CrunchyAudioPlayStreams[options.astream]}/${isDLBypass ? 'download' : 'play'}`, AuthHeaders); if (!audioPlaybackReq.ok || !audioPlaybackReq.res) { console.warn('Request Audio Stream URLs FAILED!'); } else { @@ -1695,9 +1710,17 @@ export default class Crunchy implements ServiceClass { 'hardsub_locale': stream.hlang }; } - derivedPlaystreams[''] = { - url: audioStream.url, - hardsub_locale: '' + if (isDLBypass) { + audioStream.token = videoStream.token; + derivedPlaystreams[''] = { + url: this.convertDownloadToPlayback(audioStream.url, videoStream.url), + hardsub_locale: '' + }; + } else { + derivedPlaystreams[''] = { + url: audioStream.url, + hardsub_locale: '' + }; }; pbData.meta = { audio_locale: audioStream.audioLocale, diff --git a/modules/module.api-urls.ts b/modules/module.api-urls.ts index cf2f3ce..dd0d2b3 100644 --- a/modules/module.api-urls.ts +++ b/modules/module.api-urls.ts @@ -48,7 +48,7 @@ const api: APIType = { bundlejs: 'https://static.crunchyroll.com/vilos-v2/web/vilos/js/bundle.js', // // Crunchyroll API - basic_auth_token: 'Ym1icmt4eXgzZDd1NmpzZnlsYTQ6QUlONEQ1VkVfY3Awd1Z6Zk5vUDBZcUhVcllGcDloU2c=', + basic_auth_token: 'cHVnMG43eW11YW9sa2tnaTNsYmo6WGlraXNhQ2FYRllCY1hxb09sa1NUMWg2b1pYbHdESk4=', auth: `${domain.cr_www}/auth/v1/token`, profile: `${domain.cr_www}/accounts/v1/me/profile`, search: `${domain.cr_www}/content/v2/discover/search`, @@ -64,7 +64,7 @@ const api: APIType = { cms_auth: `${domain.cr_www}/index/v2`, // // Crunchyroll Headers - crunchyDefUserAgent: 'Crunchyroll/ANDROIDTV/3.42.1_22267 (Android 16; en-US; sdk_gphone64_x86_64)', + crunchyDefUserAgent: 'Crunchyroll/ANDROIDTV/3.45.0_22272 (Android 16; en-US; sdk_gphone64_x86_64)', crunchyDefHeader: {}, crunchyAuthHeader: {}, // diff --git a/modules/module.app-args.ts b/modules/module.app-args.ts index 5697445..32e17e6 100644 --- a/modules/module.app-args.ts +++ b/modules/module.app-args.ts @@ -5,7 +5,7 @@ import { DownloadInfo } from '../@types/messageHandler'; import { HLSCallback } from './hls-download'; import leven from 'leven'; import { console } from './log'; -import { CrunchyPlayStreams } from '../@types/enums'; +import { CrunchyVideoPlayStreams, CrunchyAudioPlayStreams } from '../@types/enums'; let argvC: { [x: string]: unknown; @@ -46,9 +46,9 @@ let argvC: { q: number; x: number; // kstream: number; - cstream: keyof typeof CrunchyPlayStreams; - vstream: keyof typeof CrunchyPlayStreams; - astream: keyof typeof CrunchyPlayStreams; + cstream: keyof typeof CrunchyVideoPlayStreams; + vstream: keyof typeof CrunchyVideoPlayStreams; + astream: keyof typeof CrunchyAudioPlayStreams; tsd: boolean | undefined; partsize: number; hslang: string; diff --git a/modules/module.args.ts b/modules/module.args.ts index 43f6a0f..6ab35e6 100644 --- a/modules/module.args.ts +++ b/modules/module.args.ts @@ -1,5 +1,5 @@ import { aoSearchLocales, dubLanguageCodes, languages, searchLocales, subtitleLanguagesFilter } from './module.langsData'; -import { CrunchyPlayStreams } from '../@types/enums'; +import { CrunchyVideoPlayStreams, CrunchyAudioPlayStreams } from '../@types/enums'; const groups = { 'auth': 'Authentication:', @@ -332,7 +332,7 @@ const args: TAppArg[] = [ service: ['crunchy'], type: 'string', describe: '(Please use --vstream and --astream instead, this will deprecate soon) Select a specific Crunchyroll playback endpoint by device. Since Crunchyroll has started rolling out their new VBR encodes, we highly recommend using a TV endpoint (e.g. vidaa, samsungtv, lgtv, rokutv, chromecast, firetv, androidtv) to access the old CBR encodes. Please note: The older encodes do not include the new 192 kbps audio, the new audio is only available with the new VBR encodes.', - choices: [...Object.keys(CrunchyPlayStreams), 'none'], + choices: [...Object.keys(CrunchyVideoPlayStreams), 'none'], docDescribe: true, usage: '${device}' }, @@ -343,7 +343,7 @@ const args: TAppArg[] = [ service: ['crunchy'], type: 'string', describe: 'Select a specific Crunchyroll video playback endpoint by device.', - choices: [...Object.keys(CrunchyPlayStreams), 'none'], + choices: [...Object.keys(CrunchyVideoPlayStreams), 'none'], default: { default: 'androidtv' }, @@ -357,9 +357,9 @@ const args: TAppArg[] = [ service: ['crunchy'], type: 'string', describe: 'Select a specific Crunchyroll audio playback endpoint by device.', - choices: [...Object.keys(CrunchyPlayStreams), 'none'], + choices: [...Object.keys(CrunchyAudioPlayStreams), 'none'], default: { - default: 'androidtv' + default: 'android' }, docDescribe: true, usage: '${device}'