mirror of
https://github.com/LuanRT/YouTube.js.git
synced 2026-06-14 18:12:10 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5fe91d6642 | ||
|
|
bff65f8889 | ||
|
|
dac5eb712d | ||
|
|
2068dfb73e | ||
|
|
3e84775fd3 | ||
|
|
89fa3b27a8 | ||
|
|
0751793380 | ||
|
|
5c91c2af4a | ||
|
|
47cad4c6e1 |
@@ -14,6 +14,7 @@ overrides:
|
||||
-
|
||||
files:
|
||||
- '**/*.js'
|
||||
- '**/*.mjs'
|
||||
rules:
|
||||
'tsdoc/syntax': 'off'
|
||||
rules:
|
||||
|
||||
13
CHANGELOG.md
13
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)
|
||||
|
||||
|
||||
|
||||
13
package-lock.json
generated
13
package-lock.json
generated
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import glob from "glob";
|
||||
import glob from 'glob';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import url from 'url';
|
||||
|
||||
@@ -296,7 +296,7 @@ export default class Session extends EventEmitter {
|
||||
|
||||
const ytcfg = data[0][2];
|
||||
|
||||
const api_version = `v${ytcfg[0][0][6]}`;
|
||||
const api_version = Constants.CLIENTS.WEB.API_VERSION;
|
||||
|
||||
const [ [ device_info ], api_key ] = ytcfg;
|
||||
|
||||
|
||||
@@ -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<string, string>) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import PlaylistMetadata from '../classes/PlaylistMetadata.js';
|
||||
import PlaylistSidebarPrimaryInfo from '../classes/PlaylistSidebarPrimaryInfo.js';
|
||||
import PlaylistSidebarSecondaryInfo from '../classes/PlaylistSidebarSecondaryInfo.js';
|
||||
import PlaylistVideoThumbnail from '../classes/PlaylistVideoThumbnail.js';
|
||||
import ReelItem from '../classes/ReelItem.js';
|
||||
import VideoOwner from '../classes/VideoOwner.js';
|
||||
import Alert from '../classes/Alert.js';
|
||||
import ContinuationItem from '../classes/ContinuationItem.js';
|
||||
@@ -66,8 +67,8 @@ export default class Playlist extends Feed<IBrowseResponse> {
|
||||
return primary_info.stats[index]?.toString() || 'N/A';
|
||||
}
|
||||
|
||||
get items(): ObservedArray<PlaylistVideo> {
|
||||
return observe(this.videos.as(PlaylistVideo).filter((video) => video.style !== 'PLAYLIST_VIDEO_RENDERER_STYLE_RECOMMENDED_VIDEO'));
|
||||
get items(): ObservedArray<PlaylistVideo | ReelItem> {
|
||||
return observe(this.videos.as(PlaylistVideo, ReelItem).filter((video) => (video as PlaylistVideo).style !== 'PLAYLIST_VIDEO_RENDERER_STYLE_RECOMMENDED_VIDEO'));
|
||||
}
|
||||
|
||||
get has_continuation() {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -106,7 +106,8 @@
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.js"
|
||||
"src/**/*.js",
|
||||
"scripts/**/*.mjs",
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
|
||||
Reference in New Issue
Block a user