- fixed neighbor quality picking for youtube videos
- webm is now default for youtube downloads for all platforms except for ios
- even more readme changes
- a tiny bit of clean up
- preparing stuff for next major update
This commit is contained in:
wukko 2022-08-06 21:21:48 +06:00
parent 74edaba2b8
commit 2fae43d890
8 changed files with 32 additions and 25 deletions

View file

@ -1,12 +1,14 @@
# cobalt # cobalt
Sleek and easy to use social media downloader built with JavaScript. Best way to save content you love.
Try it now: [co.wukko.me](https://co.wukko.me/) [co.wukko.me](https://co.wukko.me/)
![cobalt logo](https://raw.githubusercontent.com/wukko/cobalt/current/src/front/icons/wide.png "cobalt logo") ![cobalt logo](https://raw.githubusercontent.com/wukko/cobalt/current/src/front/icons/wide.png "cobalt logo")
## What's cobalt? ## What's cobalt?
cobalt aims to be the ultimate social media downloader, that is efficient, pretty, and doesn't bother you with ads or privacy invasion agreement popups. It also doesn't remux anything, so you get media in best quality possible (unless you change that in settings). cobalt is social media downloader with zero bullshit. It's efficient, easy to use, and doesn't bother you with ads or privacy invasion "consent" popups.
It preserves original media quality so you get best downloads possible (unless you change that in settings).
## Supported services ## Supported services
@ -17,8 +19,7 @@ cobalt aims to be the ultimate social media downloader, that is efficient, prett
- TikTok - TikTok
- Tumblr - Tumblr
- Twitter - Twitter
- YouTube - YouTube (with HDR support)
- YouTube Music
- VK - VK
### Audio ### Audio
@ -51,8 +52,9 @@ Take English or Russian localization from [this directory](https://github.com/wu
- [ ] niconico support - [ ] niconico support
- [ ] Instagram support - [ ] Instagram support
- [ ] SoundCloud support - [ ] SoundCloud support
- [ ] Add an option to save Twitter GIFs as `.gif` instead of `.mp4`
- [ ] Quality switching for bilibili - [ ] Quality switching for bilibili
- [ ] Find a way to get TikTok videos without a watermark
- [ ] Add an option to keep watermark on TikTok videos
### Other ### Other
- [ ] Language picker in settings - [ ] Language picker in settings

View file

@ -1,7 +1,7 @@
{ {
"name": "cobalt", "name": "cobalt",
"description": "probably the friendliest social media downloader yet", "description": "save what you love",
"version": "2.2.8", "version": "2.2.9",
"author": "wukko", "author": "wukko",
"exports": "./src/cobalt.js", "exports": "./src/cobalt.js",
"type": "module", "type": "module",

View file

@ -26,6 +26,8 @@
}, },
"ffmpegArgs": { "ffmpegArgs": {
"webm": ["-c:v", "copy", "-c:a", "copy"], "webm": ["-c:v", "copy", "-c:a", "copy"],
"mp4": ["-c:v", "copy", "-c:a", "copy", "-movflags", "frag_keyframe+empty_moov"] "mp4": ["-c:v", "copy", "-c:a", "copy", "-movflags", "frag_keyframe+empty_moov"],
"bst": ["-c:a", "copy"],
"mp3": ["-ar", "48000", "-ac", "2", "-b:a", "320k"]
} }
} }

View file

@ -1,9 +1,12 @@
let isIOS = navigator.userAgent.toLowerCase().match("iphone os"); let isIOS = navigator.userAgent.toLowerCase().match("iphone os");
let switchers = { let switchers = {
"theme": ["auto", "light", "dark"], "theme": ["auto", "light", "dark"],
"youtubeFormat": ["mp4", "webm", "audio"], "youtubeFormat": ["webm", "mp4", "audio"],
"quality": ["max", "hig", "mid", "low"] "quality": ["max", "hig", "mid", "low"]
} }
let exceptions = {
"youtubeFormat": "mp4"
}
function eid(id) { function eid(id) {
return document.getElementById(id) return document.getElementById(id)
@ -98,9 +101,11 @@ function changeSwitcher(li, b, u) {
} }
if (li == "theme") detectColorScheme(); if (li == "theme") detectColorScheme();
} else { } else {
localStorage.setItem(li, switchers[li][0]); let pref = switchers[li][0];
if (isIOS && exceptions[li]) pref = exceptions[li];
localStorage.setItem(li, pref);
for (i in switchers[li]) { for (i in switchers[li]) {
(switchers[li][i] == switchers[li][0]) ? enable(`${li}-${switchers[li][0]}`) : disable(`${li}-${switchers[li][i]}`) (switchers[li][i] == pref) ? enable(`${li}-${pref}`) : disable(`${li}-${switchers[li][i]}`)
} }
} }
} }

View file

@ -24,7 +24,7 @@ export default async function (host, patternMatch, url, ip, lang, format, qualit
id: patternMatch["id"], id: patternMatch["id"],
lang: lang lang: lang
}); });
return (!r.error) ? apiJSON(1, { u: r.split('?')[0] }) : apiJSON(0, { t: r.error }); return (!r.error) ? apiJSON(1, { u: r }) : apiJSON(0, { t: r.error });
case "vk": case "vk":
r = await vk({ r = await vk({

View file

@ -39,11 +39,7 @@ export default async function (obj) {
if (parsbod.hasOwnProperty("extended_entities") && parsbod["extended_entities"].hasOwnProperty("media")) { if (parsbod.hasOwnProperty("extended_entities") && parsbod["extended_entities"].hasOwnProperty("media")) {
if (parsbod["extended_entities"]["media"][0]["type"] === "video" || parsbod["extended_entities"]["media"][0]["type"] === "animated_gif") { if (parsbod["extended_entities"]["media"][0]["type"] === "video" || parsbod["extended_entities"]["media"][0]["type"] === "animated_gif") {
let variants = parsbod["extended_entities"]["media"][0]["video_info"]["variants"] let variants = parsbod["extended_entities"]["media"][0]["video_info"]["variants"]
return variants.filter((v) => { return variants.filter((v) => { if (v["content_type"] == "video/mp4") return true; }).sort((a, b) => Number(b.bitrate) - Number(a.bitrate))[0]["url"].split('?')[0];
if (v["content_type"] == "video/mp4") {
return true
}
}).sort((a, b) => Number(b.bitrate) - Number(a.bitrate))[0]["url"]
} else { } else {
return nothing return nothing
} }

View file

@ -27,9 +27,9 @@ export default async function (obj) {
}).sort((a, b) => Number(b.bitrate) - Number(a.bitrate)); }).sort((a, b) => Number(b.bitrate) - Number(a.bitrate));
if (obj.quality != "max") { if (obj.quality != "max") {
if (videoMatch.length == 0) { if (videoMatch.length == 0) {
let ss = selectQuality("youtube", obj.quality, video[0]["height"]) let ss = selectQuality("youtube", obj.quality, video[0]["qualityLabel"].slice(0, 5).replace('p', '').trim())
videoMatch = video.filter((a) => { videoMatch = video.filter((a) => {
if (a["height"] == ss) return true; if (a["qualityLabel"].slice(0, 5).replace('p', '').trim() == ss) return true;
}) })
} else if (fullVideoMatch.length > 0) { } else if (fullVideoMatch.length > 0) {
videoMatch = [fullVideoMatch[0]] videoMatch = [fullVideoMatch[0]]

View file

@ -82,14 +82,16 @@ export async function streamAudioOnly(streamInfo, res) {
headers = { "user-agent": genericUserAgent }; headers = { "user-agent": genericUserAgent };
} }
const audio = got.get(streamInfo.urls, { isStream: true, headers: headers }); const audio = got.get(streamInfo.urls, { isStream: true, headers: headers });
const ffmpegProcess = spawn(ffmpeg, [ let format = streamInfo.filename.split('.')[streamInfo.filename.split('.').length - 1], args = [
'-loglevel', '-8', '-loglevel', '-8',
'-i', 'pipe:3', '-i', 'pipe:3',
'-vn', '-vn',
'-c:a', 'copy', ];
'-f', `${streamInfo.filename.split('.')[streamInfo.filename.split('.').length - 1]}`, args = args.concat(ffmpegArgs[format])
'pipe:4', if (streamInfo.time) args.push('-t', msToTime(streamInfo.time));
], { args.push('-f', format, 'pipe:4');
const ffmpegProcess = spawn(ffmpeg, args, {
windowsHide: true, windowsHide: true,
stdio: [ stdio: [
'inherit', 'inherit', 'inherit', 'inherit', 'inherit', 'inherit',