- bigger video/audio duration limit (3 hours instead of 2 hours and 5 minutes).
- no more unexpected errors when downloading audio from youtube.
This commit is contained in:
wukko 2023-03-01 08:37:26 +06:00
parent d196510b80
commit 2884bd9081
7 changed files with 49 additions and 24 deletions

View file

@ -1,7 +1,7 @@
{ {
"name": "cobalt", "name": "cobalt",
"description": "save what you love", "description": "save what you love",
"version": "5.1", "version": "5.1.1",
"author": "wukko", "author": "wukko",
"exports": "./src/cobalt.js", "exports": "./src/cobalt.js",
"type": "module", "type": "module",
@ -33,6 +33,6 @@
"node-cache": "^5.1.2", "node-cache": "^5.1.2",
"url-pattern": "1.0.3", "url-pattern": "1.0.3",
"xml-js": "^1.6.11", "xml-js": "^1.6.11",
"youtubei.js": "^3.0.0" "youtubei.js": "^3.1.0"
} }
} }

View file

@ -1,7 +1,6 @@
{ {
"streamLifespan": 120000, "streamLifespan": 120000,
"maxVideoDuration": 7500000, "maxVideoDuration": 10800000,
"maxAudioDuration": 7500000,
"genericUserAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36", "genericUserAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36",
"authorInfo": { "authorInfo": {
"name": "wukko", "name": "wukko",

View file

@ -3,7 +3,7 @@
"version": "5.1", "version": "5.1",
"title": "the evil has been defeated", "title": "the evil has been defeated",
"banner": "happymeowth.webp", "banner": "happymeowth.webp",
"content": "hey, ever wanted to download a youtube video without a hassle? cobalt is here to help. this update fixes all issues related to youtube downloads.\nnot only that, but it also introduces features never before seen in a downloader, such as youtube dub downloads! read below to see what's up :)\n\n<span class=\"text-backdrop\">tl;dr:</span>\n*; audio in youtube videos FINALLY no longer gets cut off.\n*; you now can pick any video resolution you want (from 360p to 8k) and any possible youtube video codec (h264/av1/vp9).\n*; you now can download youtube videos with dubs in your native language. just check settings > audio.\n*; youtube processing has been vastly sped up.\n\nok, now onto the nerdy part of changelog. this update is pretty huge and includes improvements across the board.\n\nservice improvements:\n*; all youtube functionality has been reworked. cobalt now relies on innertube apis, not web scraping.\n*; random audio cut off issue has been fixed, let me know if it ever occurs again. (closes #62, #66, #75, #88).\n*; added support for youtube dubs. currently it's using your browser's default language when enabled, but i have plans on making a picker. i'll ask people on twitter and mastodon if this feature is needed, and add a picker in next updates.\n*; instead of adding more quality presets, i added granular quality options. pick whatever you like, from 360p up to 4320p (for all services, not just youtube).\n*; replaced a format picker with codec picker for youtube. you can pick h264, av1, or vp9. all of them should work as expected (closes #88).\n*; youtube audio files are now properly matched to corresponding video files.\n*; it's now always possible to download pristine h264 720p/360p videos from youtube. these videos will work ANYWHERE, so they're default for mobile.\n*; youtube requests are no longer permanently cached, ram usage should drop even further.\n*; youtube video and audio file names now include codec and dub language when applicable.\n*; general performance of entire youtube download process has been greatly improved.\n*; vk module has been reworked to be more compact and not make use of outdated technique of quality picking. should also be way more reliable.\n\ninternal improvements:\n*; cleaned up services config, all constants have been moved directly to modules for quicker access.\n*; matching module has been slightly cleaned up.\n\ninterface improvements:\n*; many descriptions and error messages have been slightly tuned to be less wordy.\n*; unnecessary title duplications in settings have been merged into one.\n*; added more clarity to quality and codec descriptions.\n\nif you use cobalt api, please note that you have to update your creation to support new features.\n\nthis is the second batch of 5.x improvements, there's way more to come. thank you for being here, i really appreciate your support.\n\nif you want to thank me (the developer), there's a nice tab under this changelog that has \"donations\" text on it. anything helps me continue developing and hosting the friendliest media downloader :D" "content": "hey, ever wanted to download a youtube video without a hassle? cobalt is here to help. this update fixes all issues related to youtube downloads.\nnot only that, but it also introduces features never before seen in a downloader, such as youtube dub downloads! read below to see what's up :)\n\n<span class=\"text-backdrop\">tl;dr:</span>\n*; audio in youtube videos FINALLY no longer gets cut off.\n*; you now can pick any video resolution you want (from 360p to 8k) and any possible youtube video codec (h264/av1/vp9).\n*; you now can download youtube videos with dubs in your native language. just check settings > audio.\n*; youtube processing has been vastly sped up.\n\nok, now onto the nerdy part of changelog. this update is pretty huge and includes improvements across the board.\n\nservice improvements:\n*; all youtube functionality has been reworked. cobalt now relies on innertube apis, not web scraping.\n*; random audio cut off issue has been fixed, let me know if it ever occurs again. (closes #62, #66, #75, #88).\n*; added support for youtube dubs. currently it's using your browser's default language when enabled, but i have plans on making a picker. i'll ask people on twitter and mastodon if this feature is needed, and add a picker in next updates.\n*; instead of adding more quality presets, i added granular quality options. pick whatever you like, from 360p up to 4320p (for all services, not just youtube).\n*; replaced a format picker with codec picker for youtube. you can pick h264, av1, or vp9. all of them should work as expected (closes #88).\n*; youtube audio files are now properly matched to corresponding video files.\n*; it's now always possible to download pristine h264 720p/360p videos from youtube. these videos will work ANYWHERE, so they're default for mobile.\n*; youtube requests are no longer permanently cached, ram usage should drop even further.\n*; youtube video and audio file names now include codec and dub language when applicable.\n*; max video and audio duration limits have been bumped up to 3 hours.\n*; general performance of entire youtube download process has been greatly improved.\n*; vk module has been reworked to be more compact and not make use of outdated technique of quality picking. should also be way more reliable.\n\ninternal improvements:\n*; cleaned up services config, all constants have been moved directly to modules for quicker access.\n*; matching module has been slightly cleaned up.\n\ninterface improvements:\n*; many descriptions and error messages have been slightly tuned to be less wordy.\n*; unnecessary title duplications in settings have been merged into one.\n*; added more clarity to quality and codec descriptions.\n\nif you use cobalt api, please note that you have to update your creation to support new features.\n\nthis is the second batch of 5.x improvements, there's way more to come. thank you for being here, i really appreciate your support.\n\nif you want to thank me (the developer), there's a nice tab under this changelog that has \"donations\" text on it. anything helps me continue developing and hosting the friendliest media downloader :D"
}, },
"history": [{ "history": [{
"version": "5.0", "version": "5.0",

View file

@ -10,7 +10,6 @@ export const
version = packageJson.version, version = packageJson.version,
streamLifespan = config.streamLifespan, streamLifespan = config.streamLifespan,
maxVideoDuration = config.maxVideoDuration, maxVideoDuration = config.maxVideoDuration,
maxAudioDuration = config.maxAudioDuration,
genericUserAgent = config.genericUserAgent, genericUserAgent = config.genericUserAgent,
repo = packageJson["bugs"]["url"].replace('/issues', ''), repo = packageJson["bugs"]["url"].replace('/issues', ''),
authorInfo = config.authorInfo, authorInfo = config.authorInfo,

View file

@ -1,4 +1,4 @@
import { maxAudioDuration } from "../../config.js"; import { maxVideoDuration } from "../../config.js";
let cachedID = {}; let cachedID = {};
@ -58,7 +58,7 @@ export default async function(obj) {
let fileUrl = `${fileUrlBase}${fileUrlBase.includes("?") ? "&" : "?"}client_id=${clientId}&track_authorization=${json.track_authorization}`; let fileUrl = `${fileUrlBase}${fileUrlBase.includes("?") ? "&" : "?"}client_id=${clientId}&track_authorization=${json.track_authorization}`;
if (fileUrl.substring(0, 54) !== "https://api-v2.soundcloud.com/media/soundcloud:tracks:") return { error: 'ErrorEmptyDownload' }; if (fileUrl.substring(0, 54) !== "https://api-v2.soundcloud.com/media/soundcloud:tracks:") return { error: 'ErrorEmptyDownload' };
if (json.duration > maxAudioDuration) return { error: ['ErrorLengthAudioConvert', maxAudioDuration / 60000] }; if (json.duration > maxVideoDuration) return { error: ['ErrorLengthAudioConvert', maxVideoDuration / 60000] };
let file = await fetch(fileUrl).then(async (r) => { return (await r.json()).url }).catch(() => { return false }); let file = await fetch(fileUrl).then(async (r) => { return (await r.json()).url }).catch(() => { return false });
if (!file) return { error: 'ErrorCouldntFetch' }; if (!file) return { error: 'ErrorCouldntFetch' };

View file

@ -33,27 +33,20 @@ export default async function(o) {
if (info.playability_status.status !== 'OK') return { error: 'ErrorYTUnavailable' }; if (info.playability_status.status !== 'OK') return { error: 'ErrorYTUnavailable' };
if (info.basic_info.is_live) return { error: 'ErrorLiveVideo' }; if (info.basic_info.is_live) return { error: 'ErrorLiveVideo' };
let adaptive_formats = info.streaming_data.adaptive_formats.filter((e) => { let bestQuality, hasAudio, adaptive_formats = info.streaming_data.adaptive_formats.filter((e) => {
if (e["mime_type"].includes(c[o.format].codec) || e["mime_type"].includes(c[o.format].aCodec)) return true if (e["mime_type"].includes(c[o.format].codec) || e["mime_type"].includes(c[o.format].aCodec)) return true
}).sort((a, b) => Number(b.bitrate) - Number(a.bitrate)); }).sort((a, b) => Number(b.bitrate) - Number(a.bitrate));
let bestQuality = adaptive_formats[0]['quality_label'].split('p')[0];
let checkSingle = (i) => ((i['quality_label'].split('p')[0] === quality || i['quality_label'].split('p')[0] === bestQuality) && i["mime_type"].includes(c[o.format].codec)); bestQuality = adaptive_formats.find(i => i["has_video"]);
let checkBestAudio = (i) => (i["has_audio"] && !i["has_video"]); hasAudio = adaptive_formats.find(i => i["has_audio"]);
let checkBestVideo = (i) => (i['quality_label'].split('p')[0] === bestQuality && !i["has_audio"] && i["has_video"]);
let checkRightVideo = (i) => (i['quality_label'].split('p')[0] === quality && !i["has_audio"] && i["has_video"]);
if (!o.isAudioOnly && !o.isAudioMuted) {
let single = info.streaming_data.formats.find(i => checkSingle(i));
if (single) return {
type: "bridge",
urls: single.url,
filename: `youtube_${o.id}_${single.width}x${single.height}_${o.format}.${c[o.format].container}`
}
};
if (bestQuality) bestQuality = bestQuality['quality_label'].split('p')[0];
if (!bestQuality && !o.isAudioOnly || !hasAudio) return { error: 'ErrorYTTryOtherCodec' };
if (info.basic_info.duration > maxVideoDuration / 1000) return { error: ['ErrorLengthLimit', maxVideoDuration / 60000] }; if (info.basic_info.duration > maxVideoDuration / 1000) return { error: ['ErrorLengthLimit', maxVideoDuration / 60000] };
let checkBestAudio = (i) => (i["has_audio"] && !i["has_video"]);
let audio = adaptive_formats.find(i => checkBestAudio(i) && i["is_original"]); let audio = adaptive_formats.find(i => checkBestAudio(i) && i["is_original"]);
if (o.dubLang) { if (o.dubLang) {
let dubbedAudio = adaptive_formats.find(i => checkBestAudio(i) && i["language"] === o.dubLang); let dubbedAudio = adaptive_formats.find(i => checkBestAudio(i) && i["language"] === o.dubLang);
if (dubbedAudio) { if (dubbedAudio) {
@ -61,7 +54,7 @@ export default async function(o) {
isDubbed = true isDubbed = true
} }
} }
if (o.isAudioOnly) { if (hasAudio && o.isAudioOnly) {
let r = { let r = {
type: "render", type: "render",
isAudioOnly: true, isAudioOnly: true,
@ -79,6 +72,19 @@ export default async function(o) {
if (descItems[4].startsWith("Released on:")) r.fileMetadata.date = descItems[4].replace("Released on: ", '').trim(); if (descItems[4].startsWith("Released on:")) r.fileMetadata.date = descItems[4].replace("Released on: ", '').trim();
}; };
return r return r
}
let checkSingle = (i) => ((i['quality_label'].split('p')[0] === quality || i['quality_label'].split('p')[0] === bestQuality) && i["mime_type"].includes(c[o.format].codec));
let checkBestVideo = (i) => (i['quality_label'].split('p')[0] === bestQuality && !i["has_audio"] && i["has_video"]);
let checkRightVideo = (i) => (i['quality_label'].split('p')[0] === quality && !i["has_audio"] && i["has_video"]);
if (!o.isAudioOnly && !o.isAudioMuted) {
let single = info.streaming_data.formats.find(i => checkSingle(i));
if (single) return {
type: "bridge",
urls: single.url,
filename: `youtube_${o.id}_${single.width}x${single.height}_${o.format}.${c[o.format].container}`
}
}; };
let video = adaptive_formats.find(i => ((Number(quality) > Number(bestQuality)) ? checkBestVideo(i) : checkRightVideo(i))); let video = adaptive_formats.find(i => ((Number(quality) > Number(bestQuality)) ? checkBestVideo(i) : checkRightVideo(i)));

View file

@ -365,6 +365,27 @@
"code": 200, "code": 200,
"status": "stream" "status": "stream"
} }
}, {
"name": "audio bitrate higher than video, no vp9 video in response (mp3, isAudioOnly)",
"url": "https://www.youtube.com/watch?v=t5nC_ucYBrc",
"params": {
"aFormat": "mp3",
"isAudioOnly": true
},
"expected": {
"code": 200,
"status": "stream"
}
}, {
"name": "audio bitrate higher than video, no vp9 video in response (vp9)",
"url": "https://www.youtube.com/watch?v=t5nC_ucYBrc",
"params": {
"vCodec": "vp9"
},
"expected": {
"code": 400,
"status": "error"
}
}, { }, {
"name": "short, defaults", "name": "short, defaults",
"url": "https://www.youtube.com/shorts/r5FpeOJItbw", "url": "https://www.youtube.com/shorts/r5FpeOJItbw",