support for rutube, fixes, accommodations for multi lang
This commit is contained in:
@@ -34,7 +34,8 @@ const names = {
|
||||
"⌨": "keyboard",
|
||||
"📑": "boring_document",
|
||||
"🧮": "abacus",
|
||||
"😸": "cat_grin"
|
||||
"😸": "cat_grin",
|
||||
"📰": "newspaper"
|
||||
}
|
||||
let sizing = {
|
||||
18: 0.8,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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")}`
|
||||
|
||||
@@ -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) });
|
||||
}
|
||||
|
||||
30
src/modules/processing/services/rutube.js
Normal file
30
src/modules/processing/services/rutube.js
Normal 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`
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)),
|
||||
}
|
||||
|
||||
@@ -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, {
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user