mirror of
https://github.com/imputnet/cobalt.git
synced 2025-07-16 10:18:28 +00:00
Add video title to YouTube download filename
This commit is contained in:
parent
b62627058e
commit
e8a48c53f2
@ -1,4 +1,4 @@
|
|||||||
import { apiJSON } from "../sub/utils.js";
|
import { apiJSON, PostInfo } from "../sub/utils.js";
|
||||||
import { errorUnsupported, genericError, brokenLink } from "../sub/errors.js";
|
import { errorUnsupported, genericError, brokenLink } from "../sub/errors.js";
|
||||||
|
|
||||||
import loc from "../../localization/manager.js";
|
import loc from "../../localization/manager.js";
|
||||||
@ -16,6 +16,48 @@ import tumblr from "./services/tumblr.js";
|
|||||||
import vimeo from "./services/vimeo.js";
|
import vimeo from "./services/vimeo.js";
|
||||||
import soundcloud from "./services/soundcloud.js";
|
import soundcloud from "./services/soundcloud.js";
|
||||||
|
|
||||||
|
export class YouTubeFetchInfo {
|
||||||
|
/**
|
||||||
|
* ID of the YouTube video.
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
id
|
||||||
|
/**
|
||||||
|
* Quality of the YouTube video.
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
quality
|
||||||
|
/**
|
||||||
|
* Format of the YouTube video to be downloaded.
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
format
|
||||||
|
/**
|
||||||
|
* Whether only the audio of the YouTube video should be gotten.
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
isAudioOnly
|
||||||
|
/**
|
||||||
|
* Whether audio of the YouTube video should be muted.
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
isAudioMuted
|
||||||
|
/**
|
||||||
|
* Dub language to use for the video.
|
||||||
|
*
|
||||||
|
* @type {boolean | string}
|
||||||
|
*/
|
||||||
|
dubLang
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {PostInfo} obj Post information
|
||||||
|
*/
|
||||||
export default async function (host, patternMatch, url, lang, obj) {
|
export default async function (host, patternMatch, url, lang, obj) {
|
||||||
try {
|
try {
|
||||||
let r, isAudioOnly = !!obj.isAudioOnly;
|
let r, isAudioOnly = !!obj.isAudioOnly;
|
||||||
@ -44,6 +86,7 @@ export default async function (host, patternMatch, url, lang, obj) {
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "youtube":
|
case "youtube":
|
||||||
|
/** @type {YouTubeFetchInfo} */
|
||||||
let fetchInfo = {
|
let fetchInfo = {
|
||||||
id: patternMatch["id"].slice(0, 11),
|
id: patternMatch["id"].slice(0, 11),
|
||||||
quality: obj.vQuality,
|
quality: obj.vQuality,
|
||||||
@ -113,6 +156,7 @@ export default async function (host, patternMatch, url, lang, obj) {
|
|||||||
|
|
||||||
return matchActionDecider(r, host, obj.ip, obj.aFormat, isAudioOnly, lang, isAudioMuted);
|
return matchActionDecider(r, host, obj.ip, obj.aFormat, isAudioOnly, lang, isAudioMuted);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
return apiJSON(0, { t: genericError(lang, host) })
|
return apiJSON(0, { t: genericError(lang, host) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Innertube } from 'youtubei.js';
|
import { Innertube } from 'youtubei.js';
|
||||||
import { maxVideoDuration } from '../../config.js';
|
import { maxVideoDuration } from '../../config.js';
|
||||||
|
import { YouTubeFetchInfo } from '../match.js';
|
||||||
|
|
||||||
const yt = await Innertube.create();
|
const yt = await Innertube.create();
|
||||||
|
|
||||||
@ -21,7 +22,30 @@ const c = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function(o) {
|
/**
|
||||||
|
* Creates a filename to be downloaded for the requested YouTube video.
|
||||||
|
*
|
||||||
|
* @param {YouTubeFetchInfo} fi Fetch info to utilize for filename
|
||||||
|
* @param vi Video info to utilize for filename
|
||||||
|
* @param format Video format object to pull info from
|
||||||
|
* @param {string} container Video container type (file extension)
|
||||||
|
*/
|
||||||
|
function youtubeFilename(fi, vi, format, container) {
|
||||||
|
return [
|
||||||
|
'youtube',
|
||||||
|
vi.basic_info.title,
|
||||||
|
fi.id,
|
||||||
|
format.width + 'x' + format.height,
|
||||||
|
fi.format + (fi.dubLang ? "_" + fi.dubLang : ""),
|
||||||
|
].join('_') + ('.' + container);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Core logic for running the YouTube processor.
|
||||||
|
*
|
||||||
|
* @param {YouTubeFetchInfo} o Fetch information for YouTube video
|
||||||
|
*/
|
||||||
|
export default async function youtube(o) {
|
||||||
let info, isDubbed, quality = o.quality === "max" ? "9000" : o.quality; //set quality 9000(p) to be interpreted as max
|
let info, isDubbed, quality = o.quality === "max" ? "9000" : o.quality; //set quality 9000(p) to be interpreted as max
|
||||||
try {
|
try {
|
||||||
info = await yt.getBasicInfo(o.id, 'ANDROID');
|
info = await yt.getBasicInfo(o.id, 'ANDROID');
|
||||||
@ -59,7 +83,7 @@ export default async function(o) {
|
|||||||
type: "render",
|
type: "render",
|
||||||
isAudioOnly: true,
|
isAudioOnly: true,
|
||||||
urls: audio.url,
|
urls: audio.url,
|
||||||
audioFilename: `youtube_${o.id}_audio${isDubbed ? `_${o.dubLang}`:''}`,
|
audioFilename: `youtube_${o.id}_audio${isDubbed ? `_${o.dubLang}` : ''}`,
|
||||||
fileMetadata: {
|
fileMetadata: {
|
||||||
title: info.basic_info.title,
|
title: info.basic_info.title,
|
||||||
artist: info.basic_info.author.replace("- Topic", "").trim(),
|
artist: info.basic_info.author.replace("- Topic", "").trim(),
|
||||||
@ -90,7 +114,8 @@ export default async function(o) {
|
|||||||
if (video && audio) return {
|
if (video && audio) return {
|
||||||
type: "render",
|
type: "render",
|
||||||
urls: [video.url, audio.url],
|
urls: [video.url, audio.url],
|
||||||
filename: `youtube_${o.id}_${video.width}x${video.height}_${o.format}${isDubbed ? `_${o.dubLang}`:''}.${c[o.format].container}`
|
filename: youtubeFilename(o, info, video, c[o.format].container),
|
||||||
|
// filename: `youtube_${o.id}_${video.width}x${video.height}_${o.format}${isDubbed ? `_${o.dubLang}` : ''}.${c[o.format].container}`
|
||||||
};
|
};
|
||||||
|
|
||||||
return { error: 'ErrorYTTryOtherCodec' }
|
return { error: 'ErrorYTTryOtherCodec' }
|
||||||
|
@ -63,7 +63,7 @@ export function msToTime(d) {
|
|||||||
}
|
}
|
||||||
export function cleanURL(url, host) {
|
export function cleanURL(url, host) {
|
||||||
let forbiddenChars = ['}', '{', '(', ')', '\\', '%', '>', '<', '^', '*', '!', '~', ';', ':', ',', '`', '[', ']', '#', '$', '"', "'", "@"]
|
let forbiddenChars = ['}', '{', '(', ')', '\\', '%', '>', '<', '^', '*', '!', '~', ';', ':', ',', '`', '[', ']', '#', '$', '"', "'", "@"]
|
||||||
switch(host) {
|
switch (host) {
|
||||||
case "vk":
|
case "vk":
|
||||||
case "youtube":
|
case "youtube":
|
||||||
url = url.split('&')[0];
|
url = url.split('&')[0];
|
||||||
@ -95,7 +95,68 @@ export function unicodeDecode(str) {
|
|||||||
return String.fromCharCode(parseInt(unicode.replace(/\\u/g, ""), 16));
|
return String.fromCharCode(parseInt(unicode.replace(/\\u/g, ""), 16));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Post information for a given media post.
|
||||||
|
*/
|
||||||
|
export class PostInfo {
|
||||||
|
/**
|
||||||
|
* Codec used by the post's media.
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
vCodec
|
||||||
|
/**
|
||||||
|
* Quality of the post's media.
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
vQuality
|
||||||
|
/**
|
||||||
|
* Format to get of the post's media.
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
aFormat
|
||||||
|
/**
|
||||||
|
* Whether the media should be gotten as audio only.
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
isAudioOnly
|
||||||
|
/**
|
||||||
|
* Whether the TikTok watermark should be absent from the gotten media.
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
isNoTTWatermark
|
||||||
|
/**
|
||||||
|
* Whether the TikTok media should be gotten with full audio.
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
isTTFullAudio
|
||||||
|
/**
|
||||||
|
* Whether the media should be gotten with the audio muted.
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
isAudioMuted
|
||||||
|
/**
|
||||||
|
* Language to get the video dubbed in, or not to dub at all. Usually pulled from
|
||||||
|
* the request's 'Accept-Language' header, but defaults to false.
|
||||||
|
*
|
||||||
|
* @type {boolean | string}
|
||||||
|
*/
|
||||||
|
dubLang = false
|
||||||
|
/**
|
||||||
|
* Whether to get DASH video files from the Vimeo platform.
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
vimeoDash
|
||||||
|
}
|
||||||
export function checkJSONPost(obj) {
|
export function checkJSONPost(obj) {
|
||||||
|
/** @type {PostInfo} */
|
||||||
let def = {
|
let def = {
|
||||||
vCodec: "h264",
|
vCodec: "h264",
|
||||||
vQuality: "720",
|
vQuality: "720",
|
||||||
|
Loading…
Reference in New Issue
Block a user