From 9b3ebe90c56509b69b208a85bfd5ac51af5886c0 Mon Sep 17 00:00:00 2001 From: wukko Date: Tue, 1 Jul 2025 00:56:04 +0600 Subject: [PATCH 1/9] api/language-codes: remove region part of the language code and convert language codes if they're not 3 characters long --- api/src/misc/language-codes.js | 1 + api/src/processing/match-action.js | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/api/src/misc/language-codes.js b/api/src/misc/language-codes.js index c18006b5..804e3e55 100644 --- a/api/src/misc/language-codes.js +++ b/api/src/misc/language-codes.js @@ -49,5 +49,6 @@ const maps = { } export const convertLanguageCode = (code) => { + code = code.split("-")[0].split("_")[0]; return maps[code.length]?.[code.toLowerCase()] || null; } diff --git a/api/src/processing/match-action.js b/api/src/processing/match-action.js index 555705e8..6d1b0254 100644 --- a/api/src/processing/match-action.js +++ b/api/src/processing/match-action.js @@ -263,8 +263,9 @@ export default function({ // extractors usually return ISO 639-1 language codes, // but video players expect ISO 639-2, so we convert them here - if (defaultParams.fileMetadata?.sublanguage?.length === 2) { - const code = convertLanguageCode(defaultParams.fileMetadata.sublanguage); + const sublanguage = defaultParams.fileMetadata?.sublanguage; + if (sublanguage && sublanguage.length !== 3) { + const code = convertLanguageCode(sublanguage); if (code) { defaultParams.fileMetadata.sublanguage = code; } else { From 5b12622b66105991b8263955cadba3da920e73ca Mon Sep 17 00:00:00 2001 From: wukko Date: Tue, 1 Jul 2025 10:13:28 +0600 Subject: [PATCH 2/9] web/FilenamePreview: fix unlocalized strings oops --- web/i18n/en/settings.json | 2 ++ web/i18n/ru/settings.json | 2 ++ web/src/components/settings/FilenamePreview.svelte | 4 ++-- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/web/i18n/en/settings.json b/web/i18n/en/settings.json index dac6b62a..5caf67f5 100644 --- a/web/i18n/en/settings.json +++ b/web/i18n/en/settings.json @@ -85,6 +85,8 @@ "metadata.filename.preview.video": "Video Title - Video Author", "metadata.filename.preview.audio": "Audio Title - Audio Author", + "filename.preview_desc.video": "video file preview", + "filename.preview_desc.audio": "audio file preview", "metadata.file": "file metadata", "metadata.disable.title": "disable file metadata", diff --git a/web/i18n/ru/settings.json b/web/i18n/ru/settings.json index 9794aa47..937c4b54 100644 --- a/web/i18n/ru/settings.json +++ b/web/i18n/ru/settings.json @@ -80,6 +80,8 @@ "video.youtube.hls.title": "предпочитать hls для видео и аудио", "metadata.filename.preview.video": "Название Видео - Автор Видео", "metadata.filename.preview.audio": "Название Аудио - Автор Аудио", + "filename.preview_desc.video": "превью видео файла", + "filename.preview_desc.audio": "превью аудио файла", "saving.description": "предпочтительный способ сохранения файла или ссылки с кобальта. если предпочитаемый метод недоступен или что-то пойдёт не так, кобальт спросит тебя как поступить.", "accessibility.transparency.description": "уменьшает прозрачность поверхностей и выключает эффекты размытия. также может улучшить работу интерфейса на менее мощных устройствах.", "accessibility.transparency.title": "уменьшить визуальную прозрачность", diff --git a/web/src/components/settings/FilenamePreview.svelte b/web/src/components/settings/FilenamePreview.svelte index 4af25be9..35a7612f 100644 --- a/web/src/components/settings/FilenamePreview.svelte +++ b/web/src/components/settings/FilenamePreview.svelte @@ -75,7 +75,7 @@
{`${videoFilePreview}.${youtubeVideoExt}`}
-
video file preview
+
{$t("settings.filename.preview_desc.video")}
@@ -84,7 +84,7 @@
{`${audioFilePreview}.${audioFormat}`}
-
audio file preview
+
{$t("settings.filename.preview_desc.audio")}
From 810e0a865c7d6be7d6f9aea7f879b92a7775dd68 Mon Sep 17 00:00:00 2001 From: wukko Date: Tue, 1 Jul 2025 16:15:12 +0600 Subject: [PATCH 3/9] api/package: replace youtubei.js with a fork, update undici --- api/package.json | 4 +-- api/src/core/env.js | 2 +- api/src/processing/services/youtube.js | 2 +- pnpm-lock.yaml | 40 +++++++++++++++----------- 4 files changed, 28 insertions(+), 20 deletions(-) diff --git a/api/package.json b/api/package.json index 1611247d..65926020 100644 --- a/api/package.json +++ b/api/package.json @@ -26,6 +26,7 @@ "@datastructures-js/priority-queue": "^6.3.1", "@imput/psl": "^2.0.4", "@imput/version-info": "workspace:^", + "@imput/youtubei.js": "^14.0.0", "content-disposition-header": "0.6.0", "cors": "^2.8.5", "dotenv": "^16.0.1", @@ -37,9 +38,8 @@ "mime": "^4.0.4", "nanoid": "^5.0.9", "set-cookie-parser": "2.6.0", - "undici": "^5.19.1", + "undici": "^6.21.3", "url-pattern": "1.0.3", - "youtubei.js": "^14.0.0", "zod": "^3.23.8" }, "optionalDependencies": { diff --git a/api/src/core/env.js b/api/src/core/env.js index f26b3986..5b05ad76 100644 --- a/api/src/core/env.js +++ b/api/src/core/env.js @@ -1,4 +1,4 @@ -import { Constants } from "youtubei.js"; +import { Constants } from "@imput/youtubei.js"; import { services } from "../processing/service-config.js"; import { updateEnv, canonicalEnv, env as currentEnv } from "../config.js"; diff --git a/api/src/processing/services/youtube.js b/api/src/processing/services/youtube.js index 55caa835..dbf93bb8 100644 --- a/api/src/processing/services/youtube.js +++ b/api/src/processing/services/youtube.js @@ -1,7 +1,7 @@ import HLS from "hls-parser"; import { fetch } from "undici"; -import { Innertube, Session } from "youtubei.js"; +import { Innertube, Session } from "@imput/youtubei.js"; import { env } from "../../config.js"; import { getCookie } from "../cookie/manager.js"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6d67cc68..ef027b1b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,6 +19,9 @@ importers: '@imput/version-info': specifier: workspace:^ version: link:../packages/version-info + '@imput/youtubei.js': + specifier: ^14.0.0 + version: 14.0.0 content-disposition-header: specifier: 0.6.0 version: 0.6.0 @@ -53,14 +56,11 @@ importers: specifier: 2.6.0 version: 2.6.0 undici: - specifier: ^5.19.1 - version: 5.28.4 + specifier: ^6.21.3 + version: 6.21.3 url-pattern: specifier: 1.0.3 version: 1.0.3 - youtubei.js: - specifier: ^14.0.0 - version: 14.0.0 zod: specifier: ^3.23.8 version: 3.23.8 @@ -563,6 +563,9 @@ packages: '@imput/psl@2.0.4': resolution: {integrity: sha512-vuy76JX78/DnJegLuJoLpMmw11JTA/9HvlIADg/f8dDVXyxbh0jnObL0q13h+WvlBO4Gk26Pu8sUa7/h0JGQig==} + '@imput/youtubei.js@14.0.0': + resolution: {integrity: sha512-YvTnh53URPlzsmMzqF/DFHZyR9HrpgoWYHzEOklx5OCkwk1/0F/CrO9gqArXw/1oI6GjaTS2CqBd1CzyFZB07A==} + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -2080,6 +2083,10 @@ packages: resolution: {integrity: sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==} engines: {node: '>=14.0'} + undici@6.21.3: + resolution: {integrity: sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==} + engines: {node: '>=18.17'} + unist-util-stringify-position@2.0.3: resolution: {integrity: sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==} @@ -2181,9 +2188,6 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} - youtubei.js@14.0.0: - resolution: {integrity: sha512-KAFttOw+9fwwBUvBc1T7KzMNBLczDOuN/dfote8BA9CABxgx8MPgV+vZWlowdDB6DnHjSUYppv+xvJ4VNBLK9A==} - zimmerframe@1.1.2: resolution: {integrity: sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==} @@ -2396,7 +2400,8 @@ snapshots: dependencies: levn: 0.4.1 - '@fastify/busboy@2.1.1': {} + '@fastify/busboy@2.1.1': + optional: true '@fontsource/ibm-plex-mono@5.0.13': {} @@ -2423,6 +2428,13 @@ snapshots: dependencies: punycode: 2.3.1 + '@imput/youtubei.js@14.0.0': + dependencies: + '@bufbuild/protobuf': 2.2.5 + jintr: 3.3.1 + tslib: 2.6.3 + undici: 6.21.3 + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -3960,6 +3972,9 @@ snapshots: undici@5.28.4: dependencies: '@fastify/busboy': 2.1.1 + optional: true + + undici@6.21.3: {} unist-util-stringify-position@2.0.3: dependencies: @@ -4035,13 +4050,6 @@ snapshots: yocto-queue@0.1.0: {} - youtubei.js@14.0.0: - dependencies: - '@bufbuild/protobuf': 2.2.5 - jintr: 3.3.1 - tslib: 2.6.3 - undici: 5.28.4 - zimmerframe@1.1.2: {} zod@3.23.8: {} From f3992fbe337c5506ee54475c07c65180a3c5177f Mon Sep 17 00:00:00 2001 From: wukko Date: Tue, 1 Jul 2025 16:32:39 +0600 Subject: [PATCH 4/9] api/language-codes: prevent errors if code is undefined --- api/src/misc/language-codes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/misc/language-codes.js b/api/src/misc/language-codes.js index 804e3e55..1f692601 100644 --- a/api/src/misc/language-codes.js +++ b/api/src/misc/language-codes.js @@ -49,6 +49,6 @@ const maps = { } export const convertLanguageCode = (code) => { - code = code.split("-")[0].split("_")[0]; + code = code?.split("-")[0]?.split("_")[0] || ""; return maps[code.length]?.[code.toLowerCase()] || null; } From 23064f83001a63c143e8d5955c6e07d9ec35dc20 Mon Sep 17 00:00:00 2001 From: wukko Date: Fri, 4 Jul 2025 13:55:26 +0600 Subject: [PATCH 5/9] web/changelogs/11.2: add info about youtube's unavailability --- web/changelogs/11.2.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/web/changelogs/11.2.md b/web/changelogs/11.2.md index 34629289..3cc6ed29 100644 --- a/web/changelogs/11.2.md +++ b/web/changelogs/11.2.md @@ -6,7 +6,7 @@ banner: alt: "meowth plush in a forest looking at the rising sun between the trees." --- -it's summertime! even though it's been rainy for us lately, the sun is right on the horizon, just like this cobalt update. we improved local processing, added long-awaited features, and improved a ton of other stuff. **downloading from youtube is back, btw**. +it's summertime! even though it's been rainy for us lately, the sun is right on the horizon, just like this cobalt update. we improved local processing, added long-awaited features, and improved a ton of other stuff. here's what's new since 11.0: @@ -31,6 +31,8 @@ downloading from youtube on the main instance is restored! sorry that it took a hopefully it'll last for a while, but we think downloading from youtube will get significantly more annoying/complex in next few weeks-months. **right now is the best time to download everything you've been putting off**, either with cobalt or other tools. +**update**: unfortunately it did not last, youtube is unavailable on the main instance again. we will try one more way soon and update this changelog and post about it on socials accordingly. + we're not trying to scare you; it's our educated guess based on what youtube has been doing lately: - roll out of SABR & related limitations for more clients. SABR is Server ABR, Google's proprietary HLS alternative, controlled by the server. - growing potoken enforcement. From 926e9b7231e4ff1e16788782754731b2bb0db61a Mon Sep 17 00:00:00 2001 From: wukko Date: Fri, 4 Jul 2025 13:57:10 +0600 Subject: [PATCH 6/9] api/package: bump version to 11.2.1 --- api/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/package.json b/api/package.json index 65926020..7d02a08b 100644 --- a/api/package.json +++ b/api/package.json @@ -1,7 +1,7 @@ { "name": "@imput/cobalt-api", "description": "save what you love", - "version": "11.2", + "version": "11.2.1", "author": "imput", "exports": "./src/cobalt.js", "type": "module", From 773ed026b87182d1071652be86a4f964cba1d967 Mon Sep 17 00:00:00 2001 From: wukko Date: Fri, 4 Jul 2025 13:57:17 +0600 Subject: [PATCH 7/9] web/package: bump version to 11.2.3 --- web/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/package.json b/web/package.json index d31e9590..ad470d87 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "@imput/cobalt-web", - "version": "11.2.2", + "version": "11.2.3", "type": "module", "private": true, "scripts": { From b4290ecf30f17785735c21baf37403209297bcf7 Mon Sep 17 00:00:00 2001 From: wukko Date: Fri, 4 Jul 2025 15:51:59 +0600 Subject: [PATCH 8/9] api/vimeo: use bearer, update headers, better error handling --- api/src/processing/services/vimeo.js | 68 +++++++++++++++++++++++++--- 1 file changed, 62 insertions(+), 6 deletions(-) diff --git a/api/src/processing/services/vimeo.js b/api/src/processing/services/vimeo.js index 7a65f17a..7be3a4b6 100644 --- a/api/src/processing/services/vimeo.js +++ b/api/src/processing/services/vimeo.js @@ -15,7 +15,46 @@ const resolutionMatch = { "426": 240 } -const requestApiInfo = (videoId, password) => { +const genericHeaders = { + Accept: 'application/vnd.vimeo.*+json; version=3.4.10', + 'User-Agent': 'Vimeo/11.13.0 (com.vimeo; build:250619.102023.0; iOS 18.5.0) Alamofire/5.9.0 VimeoNetworking/5.0.0', + Authorization: 'Basic MTMxNzViY2Y0NDE0YTQ5YzhjZTc0YmU0NjVjNDQxYzNkYWVjOWRlOTpHKzRvMmgzVUh4UkxjdU5FRW80cDNDbDhDWGR5dVJLNUJZZ055dHBHTTB4V1VzaG41bEx1a2hiN0NWYWNUcldSSW53dzRUdFRYZlJEZmFoTTArOTBUZkJHS3R4V2llYU04Qnl1bERSWWxUdXRidjNqR2J4SHFpVmtFSUcyRktuQw==', + 'Accept-Language': 'en-US,en;q=0.9', +} + +let bearer = ''; + +const getBearer = async (refresh = false) => { + if (bearer && !refresh) return bearer; + + const oauthResponse = await fetch( + `https://api.vimeo.com/oauth/authorize/client?sizes=216,288,300,360,640,960,1280,1920&cdm_type=fairplay`, + { + method: 'POST', + body: JSON.stringify({ + scope: 'public private purchased create edit delete interact upload stats', + grant_type: 'client_credentials', + // device_identifier is a long ass base64 string of seemingly + // random data, but it doesn't seem to be required, so we just omit it lol + device_identifier: '', + }), + headers: { + ...genericHeaders, + 'Content-Type': 'application/json', + } + } + ) + .then(a => a.json()) + .catch(() => {}); + + if (!oauthResponse || !oauthResponse.access_token) { + return; + } + + return bearer = oauthResponse.access_token; +} + +const requestApiInfo = (bearerToken, videoId, password) => { if (password) { videoId += `:${password}` } @@ -24,10 +63,8 @@ const requestApiInfo = (videoId, password) => { `https://api.vimeo.com/videos/${videoId}`, { headers: { - Accept: 'application/vnd.vimeo.*+json; version=3.4.2', - 'User-Agent': 'Vimeo/10.19.0 (com.vimeo; build:101900.57.0; iOS 17.5.1) Alamofire/5.9.0 VimeoNetworking/5.0.0', - Authorization: 'Basic MTMxNzViY2Y0NDE0YTQ5YzhjZTc0YmU0NjVjNDQxYzNkYWVjOWRlOTpHKzRvMmgzVUh4UkxjdU5FRW80cDNDbDhDWGR5dVJLNUJZZ055dHBHTTB4V1VzaG41bEx1a2hiN0NWYWNUcldSSW53dzRUdFRYZlJEZmFoTTArOTBUZkJHS3R4V2llYU04Qnl1bERSWWxUdXRidjNqR2J4SHFpVmtFSUcyRktuQw==', - 'Accept-Language': 'en' + ...genericHeaders, + Authorization: `Bearer ${bearerToken}`, } } ) @@ -151,9 +188,28 @@ export default async function(obj) { if (quality < 240) quality = 240; if (!quality || obj.isAudioOnly) quality = 9000; - const info = await requestApiInfo(obj.id, obj.password); + const bearerToken = await getBearer(); + if (!bearerToken) { + return { error: "fetch.fail" }; + } + + let info = await requestApiInfo(bearerToken, obj.id, obj.password); let response; + // auth error, try to refresh the token + if (info?.error_code === 8003) { + const newBearer = await getBearer(true); + if (!newBearer) { + return { error: "fetch.fail" }; + } + info = await requestApiInfo(newBearer, obj.id, obj.password); + } + + // if there's still no info, then return a generic error + if (!info || info.error_code) { + return { error: "fetch.empty" }; + } + if (obj.isAudioOnly) { response = await getHLS(info.config_url, { ...obj, quality }); } From 14b9a590d94b8710cfc149fac73e9e93cedc5837 Mon Sep 17 00:00:00 2001 From: wukko Date: Fri, 4 Jul 2025 15:58:57 +0600 Subject: [PATCH 9/9] api/package: bump version to 11.2.2 --- api/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/package.json b/api/package.json index 7d02a08b..162cd9fc 100644 --- a/api/package.json +++ b/api/package.json @@ -1,7 +1,7 @@ { "name": "@imput/cobalt-api", "description": "save what you love", - "version": "11.2.1", + "version": "11.2.2", "author": "imput", "exports": "./src/cobalt.js", "type": "module",