Skip to content

Commit

Permalink
Merge branch 'support/videoclip' of https://github.com/ihatespawn/cobalt
Browse files Browse the repository at this point in the history
 into support/videoclip
  • Loading branch information
ihatespawn committed Jun 10, 2024
2 parents 8ce4a93 + 1a7a3ee commit 1069aec
Show file tree
Hide file tree
Showing 15 changed files with 202 additions and 38 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "cobalt",
"description": "save what you love",
"version": "7.14.3",
"version": "7.14.4",
"author": "imput",
"exports": "./src/cobalt.js",
"type": "module",
Expand All @@ -11,9 +11,9 @@
"scripts": {
"start": "node src/cobalt",
"setup": "node src/modules/setup",
"test": "node src/test/test",
"test": "node src/util/test",
"build": "node src/modules/buildStatic",
"testFilenames": "node src/test/testFilenamePresets"
"token:youtube": "node src/util/generate-youtube-tokens"
},
"repository": {
"type": "git",
Expand Down
9 changes: 6 additions & 3 deletions src/localization/languages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@
"ChangelogPressToHide": "collapse",
"Donate": "donate",
"DonateSub": "help it stay online",
"DonateExplanation": "cobalt doesn't shove ads in your face and doesn't sell your personal data, meaning that it's <span class=\"text-backdrop\">completely free to use</span> for everyone. but development and maintenance of a media-heavy service used by over 750k people is quite costly. both in terms of time and money.\n\nif cobalt helped you in the past and you want to keep it growing and evolving, you can return the favor by making a donation!\n\nyour donation will help all cobalt users: educators, students, content creators, artists, musicians, and many, many more!\n\nin past, donations have let cobalt:\n*; increase stability and uptime to nearly 100%.\n*; speed up ALL downloads, especially heavier ones.\n*; open the api for free public use.\n*; withstand several huge user influxes with 0 downtime.\n*; add resource-intensive features (such as gif conversion).\n*; continue improving our infrastructure.\n*; keep developers happy.\n\n<span class=\"text-backdrop\">every cent matters and is extremely appreciated</span>, you can truly make a difference!\n\nif you can't donate, share cobalt with a friend! we don't get ads anywhere, so cobalt is spread by word of mouth.\nsharing is the easiest way to help achieve the goal of better internet for everyone.",
"DonateExplanation": "cobalt doesn't shove ads in your face and doesn't sell your personal data, meaning that it's <span class=\"text-backdrop\">completely free to use</span> for everyone. but development and maintenance of a media-heavy service used by over 1 million people is quite costly. both in terms of time and money.\n\nif cobalt helped you in the past and you want to keep it growing and evolving, you can return the favor by making a donation!\n\nyour donation will help all cobalt users: educators, students, content creators, artists, musicians, and many, many more!\n\nin past, donations have let cobalt:\n*; increase stability and uptime to nearly 100%.\n*; speed up ALL downloads, especially heavier ones.\n*; open the api for free public use.\n*; withstand several huge user influxes with 0 downtime.\n*; add resource-intensive features (such as gif conversion).\n*; continue improving our infrastructure.\n*; keep developers happy.\n\n<span class=\"text-backdrop\">every cent matters and is extremely appreciated</span>, you can truly make a difference!\n\nif you can't donate, share cobalt with a friend! we don't get ads anywhere, so cobalt is spread by word of mouth.\nsharing is the easiest way to help achieve the goal of better internet for everyone.",
"DonateVia": "donate via",
"SettingsVideoMute": "mute audio",
"SettingsVideoMuteExplanation": "removes audio from video downloads when possible.",
Expand All @@ -100,7 +100,7 @@
"FollowSupport": "keep in touch with cobalt for news, support, and more:",
"SourceCode": "explore source code, report issues, star or fork the repo:",
"PrivacyPolicy": "cobalt's privacy policy is simple: no data about you is ever collected or stored. zero, zilch, nada, nothing.\nwhat you download is solely your business, not mine or anyone else's.\n\nif your download requires rendering, then data about requested content is encrypted and temporarily stored in server's RAM. it's necessary for this feature to function.\n\nencrypted data is stored for <span class=\"text-backdrop\">90 seconds</span> and then permanently removed.\n\nstored data is only possible to decrypt with unique encryption keys from your download link. furthermore, the official cobalt codebase doesn't provide a way to read temporarily stored data outside of processing functions.\n\nyou can check cobalt's <a class=\"text-backdrop link\" href=\"{repo}\" target=\"_blank\">source code</a> yourself and see that everything is as stated.",
"ErrorYTUnavailable": "this youtube video is unavailable. it could be age or region restricted. try another one!",
"ErrorYTUnavailable": "this youtube video is unavailable. it could be visibility or region restricted. try another one!",
"ErrorYTTryOtherCodec": "i couldn't find anything to download with your settings. try another codec or quality in settings!",
"SettingsCodecSubtitle": "youtube codec",
"SettingsCodecDescription": "h264: best support across apps/platforms, average detail level. max quality is 1080p.\nav1: best quality, small file size, most detail. supports 8k & HDR.\nvp9: same quality as av1, but file is x2 bigger. supports 4k & HDR.\n\npick h264 if you want best compatibility.\npick av1 if you want best quality and efficiency.",
Expand Down Expand Up @@ -156,6 +156,9 @@
"SettingsYoutubeDub": "use browser language",
"SettingsYoutubeDubDescription": "uses your browser's default language for youtube dubbed audio tracks. works even if cobalt ui isn't translated to your language.",
"ErrorInvalidContentType": "invalid content type header",
"UpdateOneMillion": "1 million users and blazing speed"
"UpdateOneMillion": "1 million users and blazing speed",
"ErrorYTAgeRestrict": "this youtube video is age-restricted, so i can't see it. try another one!",
"ErrorYTLogin": "couldn't get this youtube video because it requires an account to view.\n\nthis limitation is done by google to seemingly stop scraping, affecting all 3rd party tools and even their own clients.\n\ntry again, but if issue persists, {ContactLink}.",
"ErrorYTRateLimit": "i got rate limited by youtube. try again in a few seconds, but if issue persists, {ContactLink}."
}
}
9 changes: 6 additions & 3 deletions src/localization/languages/ru.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "русский",
"substrings": {
"ContactLink": "глянь <a class=\"text-backdrop link\" href=\"{statusPage}\" target=\"_blank\">статус серверов</a> или <a class=\"text-backdrop link\" href=\"{repo}\" target=\"_blank\">напиши о проблеме на github (можно на русском)</a>"
"ContactLink": "глянь <a class=\"text-backdrop link\" href=\"{statusPage}\" target=\"_blank\">статус серверов</a> или <a class=\"text-backdrop link\" href=\"{repo}\" target=\"_blank\">напиши о проблеме на github</a>"
},
"strings": {
"AppTitleCobalt": "кобальт",
Expand Down Expand Up @@ -89,7 +89,7 @@
"ChangelogPressToHide": "скрыть",
"Donate": "донаты",
"DonateSub": "ты можешь помочь!",
"DonateExplanation": "кобальт не пихает рекламу тебе в лицо и не продаёт твои личные данные, а значит работает <span class=\"text-backdrop\">совершенно бесплатно</span> для всех. но разработка и поддержка медиа сервиса, которым пользуются более 750 тысяч людей, обходится довольно затратно.\n\nесли кобальт тебе помог и ты хочешь, чтобы он продолжал расти и развиваться, то это можно сделать через донаты!\n\nтвой донат поможет всем, кто пользуется кобальтом: преподавателям, студентам, музыкантам, художникам, контент-мейкерам и многим-многим другим!\n\nв прошлом донаты помогли кобальту:\n*; повысить стабильность и аптайм почти до 100%.\n*; ускорить ВСЕ загрузки, особенно наиболее тяжёлые.\n*; открыть api для бесплатного использования.\n*; выдержать несколько огромных наплывов пользователей без перебоев.\n*; добавить ресурсоемкие фичи (например конвертацию в gif).\n*; продолжать улучшать нашу инфраструктуру.\n*; радовать разработчиков.\n\n<span class=\"text-backdrop\">каждый донат невероятно ценится</span> и помогает кобальту развиваться!\n\nесли ты не можешь отправить донат, то поделись кобальтом с другом! мы нигде не размещаем рекламу, поэтому кобальт распространяется из уст в уста.\nподелиться - самый простой способ помочь достичь цели лучшего интернета для всех.",
"DonateExplanation": "кобальт не пихает рекламу тебе в лицо и не продаёт твои личные данные, а значит работает <span class=\"text-backdrop\">совершенно бесплатно</span> для всех. но разработка и поддержка медиа сервиса, которым пользуются более миллиона людей, обходится довольно затратно.\n\nесли кобальт тебе помог и ты хочешь, чтобы он продолжал расти и развиваться, то это можно сделать через донаты!\n\nтвой донат поможет всем, кто пользуется кобальтом: преподавателям, студентам, музыкантам, художникам, контент-мейкерам и многим-многим другим!\n\nв прошлом донаты помогли кобальту:\n*; повысить стабильность и аптайм почти до 100%.\n*; ускорить ВСЕ загрузки, особенно наиболее тяжёлые.\n*; открыть api для бесплатного использования.\n*; выдержать несколько огромных наплывов пользователей без перебоев.\n*; добавить ресурсоемкие фичи (например конвертацию в gif).\n*; продолжать улучшать нашу инфраструктуру.\n*; радовать разработчиков.\n\n<span class=\"text-backdrop\">каждый донат невероятно ценится</span> и помогает кобальту развиваться!\n\nесли ты не можешь отправить донат, то поделись кобальтом с другом! мы нигде не размещаем рекламу, поэтому кобальт распространяется из уст в уста.\nподелиться - самый простой способ помочь достичь цели лучшего интернета для всех.",
"DonateVia": "открыть",
"SettingsVideoMute": "убрать аудио",
"SettingsVideoMuteExplanation": "убирает звук при загрузке видео, но только когда это возможно.",
Expand Down Expand Up @@ -157,6 +157,9 @@
"SettingsTikTokH265Description": "скачивает видео с tiktok в 1080p и h265/hevc, когда это возможно.",
"SettingsYoutubeDub": "использовать язык браузера",
"SettingsYoutubeDubDescription": "использует главный язык браузера для аудиодорожек на youtube. работает даже если кобальт не переведён в твой язык.",
"UpdateOneMillion": "миллион и невероятная скорость"
"UpdateOneMillion": "миллион и невероятная скорость",
"ErrorYTAgeRestrict": "это видео ограничено по возрасту, поэтому я не могу его скачать. попробуй другое!",
"ErrorYTLogin": "не удалось получить это видео с youtube, т. к. для его просмотра требуется учетная запись.\n\nтакое ограничение сделано google, чтобы, по-видимому, помешать скрапингу, но в итоге ломает сторонние программы и даже собственные клиенты.\n\nпопробуй ещё раз, но если проблема останется, то {ContactLink}.",
"ErrorYTRateLimit": "youtube ограничил мне частоту запросов. попробуй ещё раз через несколько секунд, но если проблема останется, то {ContactLink}."
}
}
60 changes: 58 additions & 2 deletions src/modules/processing/services/youtube.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Innertube, Session } from 'youtubei.js';
import { env } from '../../config.js';
import { cleanString } from '../../sub/utils.js';
import { fetch } from 'undici'
import { getCookie, updateCookieValues } from '../cookie/manager.js'

const ytBase = Innertube.create().catch(e => e);

Expand All @@ -23,6 +24,26 @@ const codecMatch = {
}
}

const transformSessionData = (cookie) => {
if (!cookie)
return;

const values = cookie.values();
const REQUIRED_VALUES = [
'access_token', 'refresh_token',
'client_id', 'client_secret',
'expires'
];

if (REQUIRED_VALUES.some(x => typeof values[x] !== 'string')) {
return;
}
return {
...values,
expires: new Date(values.expires),
};
}

const cloneInnertube = async (customFetch) => {
const innertube = await ytBase;
if (innertube instanceof Error) {
Expand All @@ -40,6 +61,27 @@ const cloneInnertube = async (customFetch) => {
innertube.session.cache
);

const cookie = getCookie('youtube_oauth');
const oauthData = transformSessionData(cookie);

if (!session.logged_in && oauthData) {
await session.oauth.init(oauthData);
session.logged_in = true;
}

if (session.logged_in) {
await session.oauth.refreshIfRequired();
const oldExpiry = new Date(cookie.values().expires);
const newExpiry = session.oauth.credentials.expires;

if (oldExpiry.getTime() !== newExpiry.getTime()) {
updateCookieValues(cookie, {
...session.oauth.credentials,
expires: session.oauth.credentials.expires.toISOString()
});
}
}

const yt = new Innertube(session);
return yt;
}
Expand All @@ -61,7 +103,7 @@ export default async function(o) {
}

try {
info = await yt.getBasicInfo(o.id, 'WEB');
info = await yt.getBasicInfo(o.id, yt.session.logged_in ? 'ANDROID' : 'IOS');
} catch(e) {
if (e?.message === 'This video is unavailable') {
return { error: 'ErrorCouldntFetch' };
Expand All @@ -72,7 +114,21 @@ export default async function(o) {

if (!info) return { error: 'ErrorCantConnectToServiceAPI' };

if (info.playability_status.status !== 'OK') return { error: 'ErrorYTUnavailable' };
const playability = info.playability_status;

if (playability.status === 'LOGIN_REQUIRED') {
if (playability.reason.endsWith('bot')) {
return { error: 'ErrorYTLogin' }
}
if (playability.reason.endsWith('age')) {
return { error: 'ErrorYTAgeRestrict' }
}
}
if (playability.status === "UNPLAYABLE" && playability.reason.endsWith('request limit.')) {
return { error: 'ErrorYTRateLimit' }
}

if (playability.status !== 'OK') return { error: 'ErrorYTUnavailable' };
if (info.basic_info.is_live) return { error: 'ErrorLiveVideo' };

// return a critical error if returned video is "Video Not Available"
Expand Down
2 changes: 1 addition & 1 deletion src/modules/processing/servicesConfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"alias": "reddit videos & gifs",
"patterns": ["r/:sub/comments/:id/:title", "user/:user/comments/:id/:title"],
"subdomains": "*",
"enabled": false
"enabled": true
},
"twitter": {
"alias": "twitter videos & voice",
Expand Down
67 changes: 67 additions & 0 deletions src/modules/stream/internal-hls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { createInternalStream } from './manage.js';
import HLS from 'hls-parser';

function getURL(url) {
try {
return new URL(url);
} catch {
return null;
}
}

function transformObject(streamInfo, hlsObject) {
if (hlsObject === undefined) {
return (object) => transformObject(streamInfo, object);
}

let fullUrl;
if (getURL(hlsObject.uri)) {
fullUrl = hlsObject.uri;
} else {
fullUrl = new URL(hlsObject.uri, streamInfo.url);
}

hlsObject.uri = createInternalStream(fullUrl.toString(), streamInfo);

return hlsObject;
}

function transformMasterPlaylist(streamInfo, hlsPlaylist) {
const makeInternalStream = transformObject(streamInfo);

const makeInternalVariants = (variant) => {
variant = transformObject(streamInfo, variant);
variant.video = variant.video.map(makeInternalStream);
variant.audio = variant.audio.map(makeInternalStream);
return variant;
};
hlsPlaylist.variants = hlsPlaylist.variants.map(makeInternalVariants);

return hlsPlaylist;
}

function transformMediaPlaylist(streamInfo, hlsPlaylist) {
const makeInternalSegments = transformObject(streamInfo);
hlsPlaylist.segments = hlsPlaylist.segments.map(makeInternalSegments);
hlsPlaylist.prefetchSegments = hlsPlaylist.prefetchSegments.map(makeInternalSegments);
return hlsPlaylist;
}

const HLS_MIME_TYPES = ["application/vnd.apple.mpegurl", "audio/mpegurl", "application/x-mpegURL"];

export function isHlsRequest (req) {
return HLS_MIME_TYPES.includes(req.headers['content-type']);
}

export async function handleHlsPlaylist(streamInfo, req, res) {
let hlsPlaylist = await req.body.text();
hlsPlaylist = HLS.parse(hlsPlaylist);

hlsPlaylist = hlsPlaylist.isMasterPlaylist
? transformMasterPlaylist(streamInfo, hlsPlaylist)
: transformMediaPlaylist(streamInfo, hlsPlaylist);

hlsPlaylist = HLS.stringify(hlsPlaylist);

res.send(hlsPlaylist);
}
Loading

0 comments on commit 1069aec

Please sign in to comment.