diff --git a/README.md b/README.md index 7b59578c..fc236e03 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,14 @@ # 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") ## 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 @@ -17,8 +19,7 @@ cobalt aims to be the ultimate social media downloader, that is efficient, prett - TikTok - Tumblr - Twitter -- YouTube -- YouTube Music +- YouTube (with HDR support) - VK ### Audio @@ -51,8 +52,9 @@ Take English or Russian localization from [this directory](https://github.com/wu - [ ] niconico support - [ ] Instagram support - [ ] SoundCloud support -- [ ] Add an option to save Twitter GIFs as `.gif` instead of `.mp4` - [ ] Quality switching for bilibili +- [ ] Find a way to get TikTok videos without a watermark +- [ ] Add an option to keep watermark on TikTok videos ### Other - [ ] Language picker in settings diff --git a/package.json b/package.json index e5d1d038..e202ee43 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cobalt", - "description": "probably the friendliest social media downloader yet", - "version": "2.2.8", + "description": "save what you love", + "version": "2.2.9", "author": "wukko", "exports": "./src/cobalt.js", "type": "module", diff --git a/src/config.json b/src/config.json index c6cab141..88bbdf7b 100644 --- a/src/config.json +++ b/src/config.json @@ -26,6 +26,8 @@ }, "ffmpegArgs": { "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"] } } diff --git a/src/front/cobalt.js b/src/front/cobalt.js index edf165c5..f6dff56d 100644 --- a/src/front/cobalt.js +++ b/src/front/cobalt.js @@ -1,9 +1,12 @@ let isIOS = navigator.userAgent.toLowerCase().match("iphone os"); let switchers = { "theme": ["auto", "light", "dark"], - "youtubeFormat": ["mp4", "webm", "audio"], + "youtubeFormat": ["webm", "mp4", "audio"], "quality": ["max", "hig", "mid", "low"] } +let exceptions = { + "youtubeFormat": "mp4" +} function eid(id) { return document.getElementById(id) @@ -98,9 +101,11 @@ function changeSwitcher(li, b, u) { } if (li == "theme") detectColorScheme(); } 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]) { - (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]}`) } } } diff --git a/src/modules/match.js b/src/modules/match.js index e1836213..16665ea6 100644 --- a/src/modules/match.js +++ b/src/modules/match.js @@ -24,7 +24,7 @@ export default async function (host, patternMatch, url, ip, lang, format, qualit id: patternMatch["id"], 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": r = await vk({ diff --git a/src/modules/services/twitter.js b/src/modules/services/twitter.js index 23b6d51b..6637993d 100644 --- a/src/modules/services/twitter.js +++ b/src/modules/services/twitter.js @@ -39,11 +39,7 @@ export default async function (obj) { 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") { let variants = parsbod["extended_entities"]["media"][0]["video_info"]["variants"] - return variants.filter((v) => { - if (v["content_type"] == "video/mp4") { - return true - } - }).sort((a, b) => Number(b.bitrate) - Number(a.bitrate))[0]["url"] + 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]; } else { return nothing } diff --git a/src/modules/services/youtube.js b/src/modules/services/youtube.js index 07edd417..3cca5a6f 100644 --- a/src/modules/services/youtube.js +++ b/src/modules/services/youtube.js @@ -27,9 +27,9 @@ export default async function (obj) { }).sort((a, b) => Number(b.bitrate) - Number(a.bitrate)); if (obj.quality != "max") { 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) => { - if (a["height"] == ss) return true; + if (a["qualityLabel"].slice(0, 5).replace('p', '').trim() == ss) return true; }) } else if (fullVideoMatch.length > 0) { videoMatch = [fullVideoMatch[0]] diff --git a/src/modules/stream/types.js b/src/modules/stream/types.js index 976e7e78..8ac96c7e 100644 --- a/src/modules/stream/types.js +++ b/src/modules/stream/types.js @@ -82,14 +82,16 @@ export async function streamAudioOnly(streamInfo, res) { headers = { "user-agent": genericUserAgent }; } 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', '-i', 'pipe:3', '-vn', - '-c:a', 'copy', - '-f', `${streamInfo.filename.split('.')[streamInfo.filename.split('.').length - 1]}`, - 'pipe:4', - ], { + ]; + args = args.concat(ffmpegArgs[format]) + if (streamInfo.time) args.push('-t', msToTime(streamInfo.time)); + args.push('-f', format, 'pipe:4'); + + const ffmpegProcess = spawn(ffmpeg, args, { windowsHide: true, stdio: [ 'inherit', 'inherit', 'inherit',