From cfc9d68d2de95bf28fb8bd126e2251e4108580c7 Mon Sep 17 00:00:00 2001 From: Kelvin K <kelvin@futo.org> Date: Fri, 21 Mar 2025 00:07:43 +0100 Subject: [PATCH] Member content filter fix, playlist url fix, setting to show member content, add boolean for original audio --- YoutubeConfig.json | 15 ++++++++- YoutubeScript.js | 81 ++++++++++++++++++++++++++++++++++++---------- 2 files changed, 78 insertions(+), 18 deletions(-) diff --git a/YoutubeConfig.json b/YoutubeConfig.json index 903fc0b..79961a8 100644 --- a/YoutubeConfig.json +++ b/YoutubeConfig.json @@ -8,7 +8,7 @@ "repositoryUrl": "https://gitlab.futo.org/videostreaming/plugins/youtube", "scriptUrl": "./YoutubeScript.js", - "version": 241, + "version": 242, "iconUrl": "./youtube.png", "id": "35ae969a-a7db-11ed-afa1-0242ac120002", @@ -93,6 +93,13 @@ "type": "Boolean", "default": "false" }, + { + "variable": "allowMemberContent", + "name": "Allow Member Content", + "description": "Allow listing of member content (will prefix \"[MEMBER]\")", + "type": "Boolean", + "default": "false" + }, { "variable": "useUMP", "name": "Force Experimental UMP Streams", @@ -282,6 +289,12 @@ "changelog": { + "242": [ + "Fix: Not all members only videos being filtered out", + "Fix: Certain playlist urls not being detected properly", + "Feature: Setting 'Allow Member Content' which will show channel member content (prefixed with [MEMBER])", + "Feature: Mark which audio track is the 'original' audio (will be used in upcoming Grayjay version)" + ], "241": [ "Fix: Cipher fix" ], diff --git a/YoutubeScript.js b/YoutubeScript.js index 458601b..b9b8dac 100644 --- a/YoutubeScript.js +++ b/YoutubeScript.js @@ -64,7 +64,7 @@ const REGEX_VIDEO_CHANNEL_URL2 = new RegExp("https://(.*\\.)?youtube\\.com/user/ const REGEX_VIDEO_CHANNEL_URL3 = new RegExp("https://(.*\\.)?youtube\\.com/@.*"); const REGEX_VIDEO_CHANNEL_URL4 = new RegExp("https://(.*\\.)?youtube\\.com/c/*"); -const REGEX_VIDEO_PLAYLIST_URL = new RegExp("https://(.*\\.)?youtube\\.com/playlist\\?list=.*"); +const REGEX_VIDEO_PLAYLIST_URL = new RegExp("https://(.*\\.)?youtube\\.com/playlist\\?.*"); const REGEX_INITIAL_DATA = new RegExp("<script.*?var ytInitialData = (.*?);<\/script>"); const REGEX_INITIAL_PLAYER_DATA = new RegExp("<script.*?var ytInitialPlayerResponse = (.*?});"); @@ -1529,7 +1529,7 @@ source.searchPlaylists = function(query, type, order, filters) { return new PlaylistPager([]); }; source.isPlaylistUrl = function(url) { - return REGEX_VIDEO_PLAYLIST_URL.test(url); + return REGEX_VIDEO_PLAYLIST_URL.test(url) && (url.indexOf("?list=") > 0 || url.indexOf("&list=") > 0); }; source.getPlaylist = function (url) { log(`Getting playlist: ${url}`); @@ -2023,6 +2023,10 @@ class YTABRAudioSource extends DashManifestRawAudioSource { this.parentUrl = parentUrl; this.usedLogin = !!usedLogin; this.jsUrl = jsUrl; + if(obj.priority) + this.priority = obj.priority; + if(obj.original) + this.original = obj.original; } generate() { @@ -2781,6 +2785,10 @@ class YTAudioSource extends AudioUrlRangeSource { constructor(obj, originalUrl) { super(obj); this.originalUrl = originalUrl; + if(obj.priority) + this.priority = obj.priority; + if(obj.original) + this.original = obj.original; } getRequestModifier() { @@ -4261,6 +4269,24 @@ function getBGDataFromClientConfig(clientConfig, usedLogin) { } } +/* +function extractWeb_VideoDescriptor(initialPlayerData, jsUrl, initialData, clientConfig, parentUrl, usedLogin) { + const descriptor = extractAdaptiveFormats_VideoDescriptor(initialPlayerData?.streamingData?.adaptiveFormats, jsUrl, contextData, ""); + + if(descriptor.audioSources) { + for(let source of descriptor.audioSources){ + + } + } + if(descriptor.videoSources) { + for(let source of descriptor.videoSources){ + + } + } +} +*/ + + function extractABR_VideoDescriptor(initialPlayerData, jsUrl, initialData, clientConfig, parentUrl, usedLogin) { const abrStreamingUrl = (initialPlayerData.streamingData.serverAbrStreamingUrl) ? @@ -4268,6 +4294,10 @@ function extractABR_VideoDescriptor(initialPlayerData, jsUrl, initialData, clien if(!abrStreamingUrl) return undefined; + const hasOriginal = !!(initialPlayerData.streamingData.adaptiveFormats + ?.filter(x=>x.mimeType.startsWith("audio/")) + ?.find(x=>(x.audioTrack?.displayName?.toLowerCase()?.indexOf("original") ?? -1) >= 0)); + return new UnMuxVideoSourceDescriptor( (initialPlayerData.streamingData.adaptiveFormats .filter(x => x.mimeType.startsWith("video/webm") || x.mimeType.startsWith("video/mp4")) @@ -4312,7 +4342,7 @@ function extractABR_VideoDescriptor(initialPlayerData, jsUrl, initialData, clien const duration = parseInt(parseInt(y.approxDurationMs) / 1000) ?? 0; if (isNaN(duration)) return null; - return new YTABRAudioSource(y.itag, { + const source = new YTABRAudioSource(y.itag, { name: "UMP " + (y.audioTrack?.displayName ? y.audioTrack.displayName : codecs) + ((isAV1) ? " [AV1]" : ""), url: abrStreamingUrl, width: y.width, @@ -4322,15 +4352,24 @@ function extractABR_VideoDescriptor(initialPlayerData, jsUrl, initialData, clien codec: codecs, bitrate: y.bitrate, audioChannels: y.audioChannels, + original: (hasOriginal ? + ((y.audioTrack?.displayName?.toLowerCase()?.indexOf("original") ?? -1) >= 0) : + ((y.audioTrack?.audioIsDefault ?? false))), language: ytLangIdToLanguage(y.audioTrack?.id) }, abrStreamingUrl, y, initialPlayerData.playerConfig.mediaCommonConfig.mediaUstreamerRequestConfig.videoPlaybackUstreamerConfig, getBGDataFromClientConfig(clientConfig, usedLogin), parentUrl, usedLogin, jsUrl); + + return source; })).filter(x => x != null) ); } function extractAdaptiveFormats_VideoDescriptor(adaptiveSources, jsUrl, contextData, prefix) { - const nonce = randomString(16); + const nonce = randomString(16); + + const hasOriginal = !!(adaptiveSources + ?.filter(x=>x.mimeType.startsWith("audio/")) + ?.find(x=>(x.audioTrack?.displayName?.toLowerCase()?.indexOf("original") ?? -1) >= 0)); return adaptiveSources ? new UnMuxVideoSourceDescriptor( adaptiveSources.filter(x=>x.mimeType.startsWith("video/") && (x.url || x.cipher || x.signatureCipher)).map(y=>{ const codecs = y.mimeType.substring(y.mimeType.indexOf('codecs=\"') + 8).slice(0, -1); @@ -4391,7 +4430,7 @@ function extractAdaptiveFormats_VideoDescriptor(adaptiveSources, jsUrl, contextD if(!y.initRange?.end || !y.indexRange?.end) return null; - return new YTAudioSource({ + const source = new YTAudioSource({ name: prefix + (y.audioTrack?.displayName ? y.audioTrack.displayName : codecs), container: container, bitrate: y.bitrate, @@ -4399,16 +4438,20 @@ function extractAdaptiveFormats_VideoDescriptor(adaptiveSources, jsUrl, contextD duration: (!isNaN(duration)) ? duration : 0, container: y.mimeType.substring(0, y.mimeType.indexOf(';')), codec: codecs, - language: ytLangIdToLanguage(y.audioTrack?.id), - + language: ytLangIdToLanguage(y.audioTrack?.id), + original: (hasOriginal ? + ((y.audioTrack?.displayName?.toLowerCase()?.indexOf("original") ?? -1) >= 0) : + ((y.audioTrack?.audioIsDefault ?? false))), itagId: y.itag, initStart: parseInt(y.initRange?.start), initEnd: parseInt(y.initRange?.end), indexStart: parseInt(y.indexRange?.start), indexEnd: parseInt(y.indexRange?.end), audioChannels: y.audioChannels - }, contextData.url); - }).filter(x=>x!=null), + }, contextData.url); + + return source; + }).filter(x=>x!=null), ) : new VideoSourceDescriptor([]) } @@ -4995,8 +5038,10 @@ function extractVideoWithContextRenderer_Video(videoRenderer, contextData) { x.thumbnailOverlayTimeStatusRenderer?.accessibility?.accessibilityData?.label == "LIVE"); let isLive = liveBadges != null && liveBadges.length > 0; - const isMemberOnly = !!videoRenderer.badges?.find(x=>x?.metadataBadgeRenderer?.label == "Members only"); - if(isMemberOnly) { + const isMemberOnly = !!videoRenderer.badges?.find(x=> + x?.metadataBadgeRenderer?.label == "Members only" || + x?.metadataBadgeRenderer?.label == "Members first"); + if(isMemberOnly && !_settings.allowMemberContent) { log("MEMBER ONLY VIDEO IGNORED"); return null; } @@ -5030,7 +5075,7 @@ function extractVideoWithContextRenderer_Video(videoRenderer, contextData) { if (isLive) { return new PlatformVideo({ id: new PlatformID(PLATFORM, videoRenderer.videoId, config.id), - name: escapeUnicode(title), + name: ((isMemberOnly) ? "[MEMBER] " : "") + escapeUnicode(title), thumbnails: extractThumbnail_Thumbnails(videoRenderer.thumbnail), author: author, uploadDate: plannedDate ?? parseInt(new Date().getTime() / 1000), @@ -5043,7 +5088,7 @@ function extractVideoWithContextRenderer_Video(videoRenderer, contextData) { } else { return new PlatformVideo({ id: new PlatformID(PLATFORM, videoRenderer.videoId, config.id), - name: escapeUnicode(title), + name: ((isMemberOnly) ? "[MEMBER] " : "") + escapeUnicode(title), thumbnails: extractThumbnail_Thumbnails(videoRenderer.thumbnail), author: author, uploadDate: parseInt(extractAgoText_Timestamp(extractText_String(videoRenderer.publishedTimeText))), @@ -5063,8 +5108,10 @@ function extractVideoRenderer_Video(videoRenderer, contextData) { let isLive = (liveBadges != null && liveBadges.length > 0) || (liveOverlays != null && liveOverlays.length > 0); - const isMemberOnly = !!videoRenderer.badges?.find(x=>x.metadataBadgeRenderer?.label == "Members only"); - if(isMemberOnly) { + const isMemberOnly = !!videoRenderer.badges?.find(x=> + x?.metadataBadgeRenderer?.label == "Members only" || + x?.metadataBadgeRenderer?.label == "Members first"); + if(isMemberOnly && !_settings.allowMemberContent) { log("MEMBER ONLY VIDEO IGNORED"); return null; } @@ -5094,7 +5141,7 @@ function extractVideoRenderer_Video(videoRenderer, contextData) { if(isLive) return new PlatformVideo({ id: new PlatformID(PLATFORM, videoRenderer.videoId, config.id), - name: escapeUnicode(extractRuns_String(videoRenderer.title.runs)), + name: ((isMemberOnly) ? "[MEMBER] " : "") + escapeUnicode(extractRuns_String(videoRenderer.title.runs)), thumbnails: extractThumbnail_Thumbnails(videoRenderer.thumbnail), author: author, uploadDate: plannedDate ?? parseInt(new Date().getTime()/1000), @@ -5107,7 +5154,7 @@ function extractVideoRenderer_Video(videoRenderer, contextData) { else return new PlatformVideo({ id: new PlatformID(PLATFORM, videoRenderer.videoId, config.id), - name: escapeUnicode(extractRuns_String(videoRenderer.title.runs)), + name: ((isMemberOnly) ? "[MEMBER] " : "") + escapeUnicode(extractRuns_String(videoRenderer.title.runs)), thumbnails: extractThumbnail_Thumbnails(videoRenderer.thumbnail), author: author, uploadDate: videoRenderer.publishedTimeText ? parseInt(extractAgoText_Timestamp(videoRenderer.publishedTimeText.simpleText)) : 0, -- GitLab