diff --git a/CHANGELOG.md b/CHANGELOG.md index db9f27afb..e082c30ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## [9.1.0](https://github.com/LuanRT/YouTube.js/compare/v9.0.2...v9.1.0) (2024-02-23) + + +### Features + +* **Format:** Support caption tracks in adaptive formats ([#598](https://github.com/LuanRT/YouTube.js/issues/598)) ([bff65f8](https://github.com/LuanRT/YouTube.js/commit/bff65f8889c32813ec05bd187f3a4386fc6127c0)) + + +### Bug Fixes + +* **Playlist:** `items` getter failing if a playlist contains Shorts ([89fa3b2](https://github.com/LuanRT/YouTube.js/commit/89fa3b27a839d98aaf8bd70dd75220ee309c2bea)) +* **Session:** Don't try to extract api version from service worker ([2068dfb](https://github.com/LuanRT/YouTube.js/commit/2068dfb73eefc0e40157421d4e5b4096c0c8429c)) + ## [9.0.2](https://github.com/LuanRT/YouTube.js/compare/v9.0.1...v9.0.2) (2024-01-31) diff --git a/package-lock.json b/package-lock.json index eb1d9d4e9..82c657fee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "youtubei.js", - "version": "9.0.2", + "version": "9.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "youtubei.js", - "version": "9.0.2", + "version": "9.1.0", "funding": [ "https://github.com/sponsors/LuanRT" ], @@ -5498,8 +5498,9 @@ } }, "node_modules/undici": { - "version": "5.27.0", - "license": "MIT", + "version": "5.28.3", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.3.tgz", + "integrity": "sha512-3ItfzbrhDlINjaP0duwnNsKpDQk3acHI3gVJ1z4fmwMK31k5G9OVIAMLSIaP6w4FaGkaAkN6zaQO9LUvZ1t7VA==", "dependencies": { "@fastify/busboy": "^2.0.0" }, @@ -9138,7 +9139,9 @@ "dev": true }, "undici": { - "version": "5.27.0", + "version": "5.28.3", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.3.tgz", + "integrity": "sha512-3ItfzbrhDlINjaP0duwnNsKpDQk3acHI3gVJ1z4fmwMK31k5G9OVIAMLSIaP6w4FaGkaAkN6zaQO9LUvZ1t7VA==", "requires": { "@fastify/busboy": "^2.0.0" } diff --git a/package.json b/package.json index e7914e138..bd5af0ebc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "youtubei.js", - "version": "9.0.2", + "version": "9.1.0", "description": "A wrapper around YouTube's private API. Supports YouTube, YouTube Music, YouTube Kids and YouTube Studio (WIP).", "type": "module", "types": "./dist/src/platform/lib.d.ts", diff --git a/src/parser/classes/misc/Format.ts b/src/parser/classes/misc/Format.ts index 26b2c442c..6613d7b95 100644 --- a/src/parser/classes/misc/Format.ts +++ b/src/parser/classes/misc/Format.ts @@ -45,6 +45,7 @@ export default class Format { target_duration_dec?: number; has_audio: boolean; has_video: boolean; + has_text: boolean; language?: string | null; is_dubbed?: boolean; is_descriptive?: boolean; @@ -56,6 +57,14 @@ export default class Format { matrix_coefficients?: string; }; + caption_track?: { + display_name: string; + vss_id: string; + language_code: string; + kind?: 'asr' | 'frc'; + id: string; + }; + constructor(data: RawNode, this_response_nsig_cache?: Map) { if (this_response_nsig_cache) { this.#this_response_nsig_cache = this_response_nsig_cache; @@ -96,6 +105,7 @@ export default class Format { this.target_duration_dec = data.targetDurationSec; this.has_audio = !!data.audioBitrate || !!data.audioQuality; this.has_video = !!data.qualityLabel; + this.has_text = !!data.captionTrack; this.color_info = data.colorInfo ? { primaries: data.colorInfo.primaries?.replace('COLOR_PRIMARIES_', ''), @@ -103,25 +113,42 @@ export default class Format { matrix_coefficients: data.colorInfo.matrixCoefficients?.replace('COLOR_MATRIX_COEFFICIENTS_', '') } : undefined; - if (this.has_audio) { + if (Reflect.has(data, 'audioTrack')) { + this.audio_track = { + audio_is_default: data.audioTrack.audioIsDefault, + display_name: data.audioTrack.displayName, + id: data.audioTrack.id + }; + } + + if (Reflect.has(data, 'captionTrack')) { + this.caption_track = { + display_name: data.captionTrack.displayName, + vss_id: data.captionTrack.vssId, + language_code: data.captionTrack.languageCode, + kind: data.captionTrack.kind, + id: data.captionTrack.id + }; + } + + if (this.has_audio || this.has_text) { const args = new URLSearchParams(this.cipher || this.signature_cipher); const url_components = new URLSearchParams(args.get('url') || this.url); const xtags = url_components.get('xtags')?.split(':'); - const audio_content = xtags?.find((x) => x.startsWith('acont='))?.split('=')[1]; - this.language = xtags?.find((x: string) => x.startsWith('lang='))?.split('=')[1] || null; - this.is_dubbed = audio_content === 'dubbed'; - this.is_descriptive = audio_content === 'descriptive'; - this.is_original = audio_content === 'original' || (!this.is_dubbed && !this.is_descriptive); - - if (Reflect.has(data, 'audioTrack')) { - this.audio_track = { - audio_is_default: data.audioTrack.audioIsDefault, - display_name: data.audioTrack.displayName, - id: data.audioTrack.id - }; + + if (this.has_audio) { + const audio_content = xtags?.find((x) => x.startsWith('acont='))?.split('=')[1]; + this.is_dubbed = audio_content === 'dubbed'; + this.is_descriptive = audio_content === 'descriptive'; + this.is_original = audio_content === 'original' || (!this.is_dubbed && !this.is_descriptive); + } + + // Some text tracks don't have xtags while others do + if (this.has_text && !this.language && this.caption_track) { + this.language = this.caption_track.language_code; } } } diff --git a/src/utils/FormatUtils.ts b/src/utils/FormatUtils.ts index 57dcdb775..5b0b96324 100644 --- a/src/utils/FormatUtils.ts +++ b/src/utils/FormatUtils.ts @@ -169,9 +169,9 @@ export function chooseFormat(options: FormatOptions, streaming_data?: IStreaming if (requires_audio && !requires_video) { const audio_only = candidates.filter((format) => { if (language !== 'original') { - return !format.has_video && format.language === language; + return !format.has_video && !format.has_text && format.language === language; } - return !format.has_video && format.is_original; + return !format.has_video && !format.has_text && format.is_original; }); if (audio_only.length > 0) {