mirror of
https://github.com/wukko/cobalt.git
synced 2024-11-04 23:39:59 +00:00
added support for tumblr
- support for tumblr is back! (i tried all types of links in clear sight and everything seems to work, lmk if something doesn't) - increased stream lifespan up to 1 hour - made youtube audios download without additional conversion, speed should be a bit better but not by much cause youtube limits the audio download speed :(
This commit is contained in:
parent
6afa8448f8
commit
20798685c2
9 changed files with 49 additions and 12 deletions
|
@ -15,6 +15,7 @@ cobalt aims to be the ultimate social media downloader, that is efficient, prett
|
||||||
- douyin
|
- douyin
|
||||||
- Reddit
|
- Reddit
|
||||||
- TikTok
|
- TikTok
|
||||||
|
- Tumblr
|
||||||
- Twitter
|
- Twitter
|
||||||
- YouTube
|
- YouTube
|
||||||
- YouTube Music
|
- YouTube Music
|
||||||
|
@ -45,16 +46,17 @@ Take English or Russian localization from [this directory](https://github.com/wu
|
||||||
## TO-DO
|
## TO-DO
|
||||||
|
|
||||||
### Services
|
### Services
|
||||||
- [ ] Tumblr support
|
- [x] Tumblr support
|
||||||
- [ ] niconico support
|
- [ ] niconico support
|
||||||
- [ ] Instagram support
|
- [ ] Instagram support
|
||||||
- [ ] SoundCloud support (?)
|
- [ ] SoundCloud support
|
||||||
- [ ] Add an option to save Twitter GIFs as `.gif` instead of `.mp4`
|
- [ ] Add an option to save Twitter GIFs as `.gif` instead of `.mp4`
|
||||||
- [ ] Quality switching for bilibili
|
- [ ] Quality switching for bilibili
|
||||||
|
|
||||||
### Other
|
### Other
|
||||||
- [ ] Language picker in settings
|
- [ ] Language picker in settings
|
||||||
- [ ] Make switch buttons in settings selectable with keyboard
|
- [ ] Make switch buttons in settings selectable with keyboard
|
||||||
|
- [ ] Option to save audios in formats other than original
|
||||||
- [ ] Make cobalt fully PWA compatible (add a service worker)
|
- [ ] Make cobalt fully PWA compatible (add a service worker)
|
||||||
- [ ] Make page rendering module more versatile
|
- [ ] Make page rendering module more versatile
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"streamLifespan": 1800000,
|
"streamLifespan": 3600000,
|
||||||
"maxVideoDuration": 1920000,
|
"maxVideoDuration": 1920000,
|
||||||
"genericUserAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36",
|
"genericUserAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36",
|
||||||
"authorInfo": {
|
"authorInfo": {
|
||||||
|
|
|
@ -18,6 +18,10 @@ export async function getJSON(originalURL, ip, lang, format, quality) {
|
||||||
host = "youtube";
|
host = "youtube";
|
||||||
url = `https://youtube.com/watch?v=${url.replace("youtu.be/", "").replace("https://", "")}`;
|
url = `https://youtube.com/watch?v=${url.replace("youtu.be/", "").replace("https://", "")}`;
|
||||||
}
|
}
|
||||||
|
if (host == "tumblr" && !url.includes("blog/view")) {
|
||||||
|
if (url.slice(-1) == '/') url = url.slice(0, -1);
|
||||||
|
url = url.replace(url.split('/')[5], '');
|
||||||
|
}
|
||||||
if (host && host.length < 20 && host in patterns && patterns[host]["enabled"]) {
|
if (host && host.length < 20 && host in patterns && patterns[host]["enabled"]) {
|
||||||
for (let i in patterns[host]["patterns"]) {
|
for (let i in patterns[host]["patterns"]) {
|
||||||
patternMatch = new UrlPattern(patterns[host]["patterns"][i]).match(cleanURL(url, host).split(".com/")[1]);
|
patternMatch = new UrlPattern(patterns[host]["patterns"][i]).match(cleanURL(url, host).split(".com/")[1]);
|
||||||
|
|
|
@ -8,6 +8,7 @@ import youtube from "./services/youtube.js";
|
||||||
import vk from "./services/vk.js";
|
import vk from "./services/vk.js";
|
||||||
import tiktok from "./services/tiktok.js";
|
import tiktok from "./services/tiktok.js";
|
||||||
import douyin from "./services/douyin.js";
|
import douyin from "./services/douyin.js";
|
||||||
|
import tumblr from "./services/tumblr.js";
|
||||||
|
|
||||||
export default async function (host, patternMatch, url, ip, lang, format, quality) {
|
export default async function (host, patternMatch, url, ip, lang, format, quality) {
|
||||||
try {
|
try {
|
||||||
|
@ -113,6 +114,16 @@ export default async function (host, patternMatch, url, ip, lang, format, qualit
|
||||||
filename: r.filename, salt: process.env.streamSalt
|
filename: r.filename, salt: process.env.streamSalt
|
||||||
}) : apiJSON(0, { t: r.error });
|
}) : apiJSON(0, { t: r.error });
|
||||||
} else throw Error()
|
} else throw Error()
|
||||||
|
case "tumblr":
|
||||||
|
if ((patternMatch["id"] && patternMatch["id"].length < 21) ||
|
||||||
|
(patternMatch["id"] && patternMatch["id"].length < 21 &&
|
||||||
|
patternMatch["user"] && patternMatch["user"].length <= 32)) {
|
||||||
|
let r = await tumblr({
|
||||||
|
id: patternMatch["id"], url: url, user: patternMatch["user"] ? patternMatch["user"] : false,
|
||||||
|
lang: lang
|
||||||
|
});
|
||||||
|
return (!r.error) ? apiJSON(1, { u: r.split('?')[0] }) : apiJSON(0, { t: r.error })
|
||||||
|
} else throw Error()
|
||||||
default:
|
default:
|
||||||
return apiJSON(0, { t: errorUnsupported(lang) })
|
return apiJSON(0, { t: errorUnsupported(lang) })
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,12 +54,8 @@
|
||||||
"enabled": true
|
"enabled": true
|
||||||
},
|
},
|
||||||
"tumblr": {
|
"tumblr": {
|
||||||
"patterns": ["post/:id"],
|
"patterns": ["post/:id", "blog/view/:user/:id"],
|
||||||
"enabled": false
|
"enabled": true
|
||||||
},
|
|
||||||
"facebook": {
|
|
||||||
"patterns": [":pageid/:type/:postid"],
|
|
||||||
"enabled": false
|
|
||||||
},
|
},
|
||||||
"instagram": {
|
"instagram": {
|
||||||
"patterns": [":type/:id"],
|
"patterns": [":type/:id"],
|
||||||
|
|
24
src/modules/services/tumblr.js
Normal file
24
src/modules/services/tumblr.js
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import got from "got";
|
||||||
|
import loc from "../../localization/manager.js";
|
||||||
|
import { genericUserAgent } from "../config.js";
|
||||||
|
|
||||||
|
export default async function(obj) {
|
||||||
|
try {
|
||||||
|
let user = obj.user ? obj.user : obj.url.split('.')[0].replace('https://', '');
|
||||||
|
if (user.length <= 32) {
|
||||||
|
let html = await got.get(`https://${user}.tumblr.com/post/${obj.id}`, { headers: { "user-agent": genericUserAgent } });
|
||||||
|
html.on('error', (err) => {
|
||||||
|
return { error: loc(obj.lang, 'ErrorCouldntFetch', 'tumblr') };
|
||||||
|
});
|
||||||
|
html = html.body
|
||||||
|
if (html.includes('<!-- GOOGLE CAROUSEL --><script type="application/ld+json">')) {
|
||||||
|
let json = JSON.parse(html.split('<!-- GOOGLE CAROUSEL --><script type="application/ld+json">')[1].split('</script>')[0])
|
||||||
|
if (json["video"] && json["video"]["contentUrl"]) {
|
||||||
|
return json["video"]["contentUrl"]
|
||||||
|
} else return { error: loc(obj.lang, 'ErrorEmptyDownload') }
|
||||||
|
} else return { error: loc(obj.lang, 'ErrorBrokenLink', 'tumblr') }
|
||||||
|
} else return { error: loc(obj.lang, 'ErrorBrokenLink', 'tumblr') }
|
||||||
|
} catch (e) {
|
||||||
|
return { error: loc(obj.lang, 'ErrorBadFetch') };
|
||||||
|
}
|
||||||
|
}
|
|
@ -54,7 +54,7 @@ export default async function (obj) {
|
||||||
return { type: "render", urls: [video[0]["url"], audio[0]["url"]], time: video[0]["approxDurationMs"],
|
return { type: "render", urls: [video[0]["url"], audio[0]["url"]], time: video[0]["approxDurationMs"],
|
||||||
filename: `youtube_${obj.id}_${video[0]["width"]}x${video[0]["height"]}.${video[0]["container"]}` };
|
filename: `youtube_${obj.id}_${video[0]["width"]}x${video[0]["height"]}.${video[0]["container"]}` };
|
||||||
} else if (audio.length > 0) {
|
} else if (audio.length > 0) {
|
||||||
return { type: "render", isAudioOnly: true, urls: [audio[0]["url"]], filename: `youtube_${obj.id}_${audio[0]["audioBitrate"]}kbps.opus` };
|
return { type: "bridge", isAudioOnly: true, urls: audio[0]["url"], filename: `youtube_${obj.id}_${audio[0]["audioBitrate"]}kbps.${audio[0]["container"] == "webm" ? "opus" : "m4a"}` };
|
||||||
} else {
|
} else {
|
||||||
return { error: loc(obj.lang, 'ErrorBadFetch') };
|
return { error: loc(obj.lang, 'ErrorBadFetch') };
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ export default function(res, ip, id, hmac, exp) {
|
||||||
try {
|
try {
|
||||||
let streamInfo = verifyStream(ip, id, hmac, exp, process.env.streamSalt);
|
let streamInfo = verifyStream(ip, id, hmac, exp, process.env.streamSalt);
|
||||||
if (!streamInfo.error) {
|
if (!streamInfo.error) {
|
||||||
if (streamInfo.isAudioOnly) {
|
if (streamInfo.isAudioOnly && streamInfo.type == "render") {
|
||||||
streamAudioOnly(streamInfo, res);
|
streamAudioOnly(streamInfo, res);
|
||||||
} else {
|
} else {
|
||||||
switch (streamInfo.type) {
|
switch (streamInfo.type) {
|
||||||
|
|
|
@ -81,7 +81,7 @@ export async function streamAudioOnly(streamInfo, res) {
|
||||||
if (streamInfo.service == "bilibili") {
|
if (streamInfo.service == "bilibili") {
|
||||||
headers = { "user-agent": genericUserAgent };
|
headers = { "user-agent": genericUserAgent };
|
||||||
}
|
}
|
||||||
const audio = got.get(streamInfo.urls[0], { isStream: true, headers: headers });
|
const audio = got.get(streamInfo.urls, { isStream: true, headers: headers });
|
||||||
const ffmpegProcess = spawn(ffmpeg, [
|
const ffmpegProcess = spawn(ffmpeg, [
|
||||||
'-loglevel', '-8',
|
'-loglevel', '-8',
|
||||||
'-i', 'pipe:3',
|
'-i', 'pipe:3',
|
||||||
|
|
Loading…
Reference in a new issue