support for rutube, fixes, accommodations for multi lang

This commit is contained in:
wukko
2023-09-16 23:38:07 +06:00
parent 05bb7bcd07
commit e721cf9878
16 changed files with 164 additions and 38 deletions

View File

@@ -34,7 +34,8 @@ const names = {
"⌨": "keyboard",
"📑": "boring_document",
"🧮": "abacus",
"😸": "cat_grin"
"😸": "cat_grin",
"📰": "newspaper"
}
let sizing = {
18: 0.8,

View File

@@ -1,4 +1,4 @@
import { celebrations } from "../config.js";
import { authorInfo, celebrations } from "../config.js";
import emoji from "../emoji.js";
export const backButtonSVG = `<svg width="22" height="22" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
@@ -160,6 +160,16 @@ export function popupWithBottomButtons(obj) {
export function socialLink(emji, name, handle, url) {
return `<div class="cobalt-support-link">${emji} ${name}: <a class="text-backdrop link" href="${url}" target="_blank">${handle}</a></div>`
}
export function socialLinks(lang) {
let links = authorInfo.support[lang] ? authorInfo.support[lang] : authorInfo.support.default;
let r = ``;
for (let i in links) {
r += socialLink(
emoji(links[i].emoji), i, links[i].handle, links[i].url
)
}
return r
}
export function settingsCategory(obj) {
return `<div id="settings-${obj.name}" class="settings-category">
<div class="category-title">${obj.title ? obj.title : obj.name}</div>

View File

@@ -1,4 +1,4 @@
import { checkbox, collapsibleList, explanation, footerButtons, multiPagePopup, popup, popupWithBottomButtons, sep, settingsCategory, switcher, socialLink, urgentNotice, keyboardShortcuts, webLoc } from "./elements.js";
import { checkbox, collapsibleList, explanation, footerButtons, multiPagePopup, popup, popupWithBottomButtons, sep, settingsCategory, switcher, socialLink, socialLinks, urgentNotice, keyboardShortcuts, webLoc } from "./elements.js";
import { services as s, authorInfo, version, repo, donations, supportedAudio } from "../config.js";
import { getCommitInfo } from "../sub/currentCommit.js";
import loc from "../../localization/manager.js";
@@ -71,7 +71,7 @@ export default function(obj) {
<link rel="stylesheet" href="fonts/notosansmono.css" rel="preload" />
<link rel="stylesheet" href="cobalt.css" />
<link rel="me" href="${authorInfo.support.mastodon.url}">
<link rel="me" href="${authorInfo.support.default.mastodon.url}">
<noscript><div style="margin: 2rem;">${t('NoScriptMessage')}</div></noscript>
</head>
@@ -99,7 +99,11 @@ export default function(obj) {
text: collapsibleList([{
name: "services",
title: `${emoji("🔗")} ${t("CollapseServices")}`,
body: `${enabledServices}<br/><br/>${t("ServicesNote")}`
body: `${enabledServices}`
+ `<div class="explanation embedded">${t("SupportNotAffiliated")}`
+ `${obj.lang === "ru" ? `<br>${t("SupportMetaNoticeRU")}` : ''}`
+ `</div>`
+ `${t("ServicesNote")}`
}, {
name: "keyboard",
title: `${emoji("⌨")} ${t("CollapseKeyboard")}`,
@@ -143,19 +147,11 @@ export default function(obj) {
name: "support",
title: `${emoji("❤️‍🩹")} ${t("CollapseSupport")}`,
body:
`${t("SupportSelfTroubleshooting")}<br/><br/>
${t("FollowSupport")}<br/>
${socialLink(
emoji("🐦"), "twitter", authorInfo.support.twitter.handle, authorInfo.support.twitter.url
)}
${socialLink(
emoji("👾"), "discord", authorInfo.support.discord.handle, authorInfo.support.discord.url
)}
${socialLink(
emoji("🐘"), "mastodon", authorInfo.support.mastodon.handle, authorInfo.support.mastodon.url
)}<br/>
${t("SourceCode")}<br/>
${socialLink(
`${t("SupportSelfTroubleshooting")}<br/><br/>`
+ `${t("FollowSupport")}<br/>`
+ `${socialLinks(obj.lang)}<br/>`
+ `${t("SourceCode")}<br/>`
+ `${socialLink(
emoji("🐙"), "github", repo.replace("https://github.com/", ''), repo
)}<br/>
${t("SupportNote")}`

View File

@@ -20,6 +20,7 @@ import vine from "./services/vine.js";
import pinterest from "./services/pinterest.js";
import streamable from "./services/streamable.js";
import twitch from "./services/twitch.js";
import rutube from "./services/rutube.js";
export default async function (host, patternMatch, url, lang, obj) {
try {
@@ -130,6 +131,13 @@ export default async function (host, patternMatch, url, lang, obj) {
isAudioOnly: obj.isAudioOnly
});
break;
case "rutube":
r = await rutube({
id: patternMatch["id"],
quality: obj.vQuality,
isAudioOnly: isAudioOnly
});
break;
default:
return apiJSON(0, { t: errorUnsupported(lang) });
}

View File

@@ -0,0 +1,30 @@
import HLS from 'hls-parser';
import { maxVideoDuration } from "../../config.js";
export default async function(obj) {
let quality = obj.quality === "max" ? "9000" : obj.quality;
let play = await fetch(`https://rutube.ru/api/play/options/${obj.id}/?no_404=true&referer&pver=v2`).then((r) => { return r.json() }).catch(() => { return false });
if (!play) return { error: 'ErrorCouldntFetch' };
if ("hls" in play.live_streams) return { error: 'ErrorLiveVideo' };
if (!play.video_balancer || play.detail) return { error: 'ErrorEmptyDownload' };
if (play.duration > maxVideoDuration) return { error: ['ErrorLengthLimit', maxVideoDuration / 60000] };
let m3u8 = await fetch(play.video_balancer.m3u8).then((r) => { return r.text() }).catch(() => { return false });
if (!m3u8) return { error: 'ErrorCouldntFetch' };
m3u8 = HLS.parse(m3u8).variants.sort((a, b) => Number(b.bandwidth) - Number(a.bandwidth));
let bestQuality = m3u8[0];
if (Number(quality) < bestQuality.resolution.height) {
bestQuality = m3u8.find((i) => (Number(quality) === i["resolution"].height));
}
return {
urls: bestQuality.uri,
isM3U8: true,
audioFilename: `rutube_${play.id}_audio`,
filename: `rutube_${play.id}_${bestQuality.resolution.width}x${bestQuality.resolution.height}.mp4`
}
}

View File

@@ -78,6 +78,12 @@
"tld": "tv",
"patterns": [":channel/clip/:clip"],
"enabled": true
},
"rutube": {
"alias": "rutube videos",
"tld": "ru",
"patterns": ["video/:id", "play/embed/:id"],
"enabled": true
}
}
}

View File

@@ -35,4 +35,6 @@ export const testers = {
"streamable": (patternMatch) => (patternMatch["id"] && patternMatch["id"].length === 6),
"twitch": (patternMatch) => ((patternMatch["channel"] && patternMatch["clip"] && patternMatch["clip"].length <= 100)),
"rutube": (patternMatch) => ((patternMatch["id"] && patternMatch["id"].length === 32)),
}

View File

@@ -152,7 +152,7 @@ export function streamVideoOnly(streamInfo, res) {
'-c', 'copy'
]
if (streamInfo.mute) args.push('-an');
if (streamInfo.service === "vimeo") args.push('-bsf:a', 'aac_adtstoasc');
if (streamInfo.service === "vimeo" || streamInfo.service === "rutube") args.push('-bsf:a', 'aac_adtstoasc');
if (format === "mp4") args.push('-movflags', 'faststart+frag_keyframe+empty_moov');
args.push('-f', format, 'pipe:3');
const ffmpegProcess = spawn(ffmpeg, args, {

View File

@@ -50,7 +50,7 @@ export function metadataManager(obj) {
return commands;
}
export function cleanURL(url, host) {
switch(host) {
switch (host) {
case "vk":
url = url.includes('clip') ? url.split('&')[0] : url.split('?')[0];
break;