diff --git a/README.md b/README.md index ec0598f2..5bdc8a7a 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ It preserves original media quality so you get best downloads possible (unless y - YouTube (with HDR support) ### Only video +- Vimeo - VK ### Only audio @@ -45,8 +46,8 @@ Take English or Russian localization from [this directory](https://github.com/wu - You can add wordplays or puns if it feels natural to do so. - Even though I love cursing, keep that away from translations. - Always check if there are issues in UI with your localization. -- There's no need to translate `ChangelogContentTitle` and `ChangelogContent`, because those are very often changed. -- Add "(in english)" to `ChangelogLastCommit` and `ChangelogLastMajor`, because those are almost always kept exclusively in English. Remove that phrase if you do translate major update changelog. +- There's no need to translate `ChangelogContentTitle` and `ChangelogContent`, because those are very often changed. You can remove both of them from your translation file. +- Add "(in english)" in translation language to `ChangelogLastCommit` and `ChangelogLastMajor`, because those are almost always kept exclusively in English. Remove that phrase if you do translate major update changelog. - Be nice. ## TO-DO @@ -60,6 +61,7 @@ Take English or Russian localization from [this directory](https://github.com/wu - [ ] Add an option to keep watermark on TikTok videos ### Other +- [ ] Remake video quality picking (do it more like I did in Vimeo module) - [ ] Add support for emoji in localization - [ ] Language picker in settings - [ ] Make cobalt fully PWA compatible (add a service worker) diff --git a/src/config.json b/src/config.json index 53a49ea6..69b9bd90 100644 --- a/src/config.json +++ b/src/config.json @@ -29,7 +29,7 @@ "mid": "720", "low": "480" }, - "supportedAudio": ["mp3", "ogg", "opus"], + "supportedAudio": ["mp3", "ogg", "wav", "opus"], "ffmpegArgs": { "webm": ["-c:v", "copy", "-c:a", "copy"], "mp4": ["-c:v", "copy", "-c:a", "copy", "-movflags", "frag_keyframe+empty_moov"], diff --git a/src/front/cobalt.js b/src/front/cobalt.js index 3bea8f6a..1157d927 100644 --- a/src/front/cobalt.js +++ b/src/front/cobalt.js @@ -5,7 +5,7 @@ let switchers = { "theme": ["auto", "light", "dark"], "ytFormat": ["webm", "mp4"], "quality": ["max", "hig", "mid", "low"], - "audioFormat": ["best", "mp3", "ogg", "opus"] + "audioFormat": ["best", "mp3", "ogg", "wav", "opus"] } let exceptions = { // fuck you apple "ytFormat": "mp4", diff --git a/src/modules/match.js b/src/modules/match.js index 9450a16e..00f6c20e 100644 --- a/src/modules/match.js +++ b/src/modules/match.js @@ -12,6 +12,7 @@ import tiktok from "./services/tiktok.js"; import douyin from "./services/douyin.js"; import tumblr from "./services/tumblr.js"; import matchActionDecider from "./sub/matchActionDecider.js"; +import vimeo from "./services/vimeo.js"; export default async function (host, patternMatch, url, ip, lang, format, quality, audioFormat, isAudioOnly) { try { @@ -84,6 +85,12 @@ export default async function (host, patternMatch, url, ip, lang, format, qualit lang: lang }); break; + case "vimeo": + r = await vimeo({ + id: patternMatch["id"], quality: quality, + lang: lang + }); + break; default: return apiJSON(0, { t: errorUnsupported(lang) }); } diff --git a/src/modules/services/vimeo.js b/src/modules/services/vimeo.js new file mode 100644 index 00000000..220c9f63 --- /dev/null +++ b/src/modules/services/vimeo.js @@ -0,0 +1,38 @@ +import got from "got"; +import loc from "../../localization/manager.js"; +import { genericUserAgent, quality } from "../config.js"; + +export default async function(obj) { + try { + let api = await got.get(`https://player.vimeo.com/video/${obj.id}/config`, { headers: { "user-agent": genericUserAgent } }); + api.on('error', (err) => { + return { error: loc(obj.lang, 'ErrorCouldntFetch', 'vimeo') }; + }); + api = api.body + if (api.includes('}}},"progressive":[{')) { + api = JSON.parse(api) + if (api["request"]["files"]["progressive"]) { + let all = api["request"]["files"]["progressive"].sort((a, b) => Number(b.width) - Number(a.width)); + let best = all[0] + try { + if (obj.quality != "max") { + let pref = parseInt(quality[obj.quality]) + for (let i in all) { + let currQuality = parseInt(all[i]["quality"].replace('p', '')) + if (currQuality < pref) { + break; + } else if (currQuality == pref) { + best = all[i] + } + } + } + } catch (e) { + best = all[0] + } + return { urls: best["url"], audioFilename: loc(obj.lang, 'ErrorEmptyDownload') } + } else return { error: loc(obj.lang, 'ErrorEmptyDownload') } + } else return { error: loc(obj.lang, 'ErrorBrokenLink', 'vimeo') } + } catch (e) { + return { error: loc(obj.lang, 'ErrorBadFetch') }; + } +} diff --git a/src/modules/services/vk.js b/src/modules/services/vk.js index 4ccfc8ba..c1820b88 100644 --- a/src/modules/services/vk.js +++ b/src/modules/services/vk.js @@ -28,7 +28,7 @@ export default async function(obj) { let userQuality = selectQuality('vk', obj.quality, Object.entries(services.vk.quality_match).reduce((r, [k, v]) => { r[v] = k; return r;})[maxQuality]) let id = js["player"]["params"][0][selectedQuality].split("id=")[1] if (selectedQuality in js["player"]["params"][0]) { - return { urls: js["player"]["params"][0][selectedQuality].replace(`type=${maxQuality}`, `type=${services.vk.quality_match[userQuality]}`), filename: `vk_${js["player"]["params"][0][selectedQuality].split("id=")[1]}_${attr['width']}x${attr['height']}.mp4`, audioFilename: loc(obj.lang, 'ErrorEmptyDownload') }; + return { urls: js["player"]["params"][0][selectedQuality].replace(`type=${maxQuality}`, `type=${services.vk.quality_match[userQuality]}`), filename: `vk_${id}_${attr['width']}x${attr['height']}.mp4`, audioFilename: loc(obj.lang, 'ErrorEmptyDownload') }; } else { return { error: loc(obj.lang, 'ErrorEmptyDownload') }; } diff --git a/src/modules/servicesConfig.json b/src/modules/servicesConfig.json index 16c879a1..1e9be319 100644 --- a/src/modules/servicesConfig.json +++ b/src/modules/servicesConfig.json @@ -68,5 +68,9 @@ "douyin": { "patterns": ["video/:postId", ":id"], "enabled": true + }, + "vimeo": { + "patterns": [":id"], + "enabled": true } } diff --git a/src/modules/servicesPatternTesters.js b/src/modules/servicesPatternTesters.js index ce2b1e05..8b888226 100644 --- a/src/modules/servicesPatternTesters.js +++ b/src/modules/servicesPatternTesters.js @@ -19,4 +19,6 @@ export let testers = { "tumblr": (patternMatch) => ((patternMatch["id"] && patternMatch["id"].length < 21) || (patternMatch["id"] && patternMatch["id"].length < 21 && patternMatch["user"] && patternMatch["user"].length <= 32)), + + "vimeo": (patternMatch) => ((patternMatch["id"] && patternMatch["id"].length <= 11)), }; \ No newline at end of file diff --git a/src/modules/sub/matchActionDecider.js b/src/modules/sub/matchActionDecider.js index ba6ccbe3..a450f3ea 100644 --- a/src/modules/sub/matchActionDecider.js +++ b/src/modules/sub/matchActionDecider.js @@ -41,6 +41,8 @@ export default function(r, host, ip, audioFormat, isAudioOnly) { }) case "tumblr": return apiJSON(1, { u: r.urls }) + case "vimeo": + return apiJSON(1, { u: r.urls }) } } else { let type = "render" @@ -55,7 +57,7 @@ export default function(r, host, ip, audioFormat, isAudioOnly) { type = "bridge" } } - if (host == "reddit" && r.typeId == 1 || host == "vk") return apiJSON(0, { t: r.audioFilename }); + if (host == "reddit" && r.typeId == 1 || host == "vk" || host == "vimeo") return apiJSON(0, { t: r.audioFilename }); return apiJSON(2, { type: type, u: Array.isArray(r.urls) ? r.urls[1] : r.urls, service: host, ip: ip,