diff --git a/src/modules/processing/match.js b/src/modules/processing/match.js index 82b9d8fc..8e8056c8 100644 --- a/src/modules/processing/match.js +++ b/src/modules/processing/match.js @@ -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 loc from "../../localization/manager.js"; @@ -16,6 +16,48 @@ import tumblr from "./services/tumblr.js"; import vimeo from "./services/vimeo.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) { try { let r, isAudioOnly = !!obj.isAudioOnly; @@ -44,6 +86,7 @@ export default async function (host, patternMatch, url, lang, obj) { }); break; case "youtube": + /** @type {YouTubeFetchInfo} */ let fetchInfo = { id: patternMatch["id"].slice(0, 11), 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); } catch (e) { + console.error(e); return apiJSON(0, { t: genericError(lang, host) }) } } diff --git a/src/modules/processing/services/youtube.js b/src/modules/processing/services/youtube.js index 25266547..af826617 100644 --- a/src/modules/processing/services/youtube.js +++ b/src/modules/processing/services/youtube.js @@ -1,5 +1,6 @@ import { Innertube } from 'youtubei.js'; import { maxVideoDuration } from '../../config.js'; +import { YouTubeFetchInfo } from '../match.js'; 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 try { info = await yt.getBasicInfo(o.id, 'ANDROID'); @@ -59,7 +83,7 @@ export default async function(o) { type: "render", isAudioOnly: true, urls: audio.url, - audioFilename: `youtube_${o.id}_audio${isDubbed ? `_${o.dubLang}`:''}`, + audioFilename: `youtube_${o.id}_audio${isDubbed ? `_${o.dubLang}` : ''}`, fileMetadata: { title: info.basic_info.title, artist: info.basic_info.author.replace("- Topic", "").trim(), @@ -90,7 +114,8 @@ export default async function(o) { if (video && audio) return { type: "render", 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' } diff --git a/src/modules/sub/utils.js b/src/modules/sub/utils.js index db275032..ec74c918 100644 --- a/src/modules/sub/utils.js +++ b/src/modules/sub/utils.js @@ -63,7 +63,7 @@ export function msToTime(d) { } export function cleanURL(url, host) { let forbiddenChars = ['}', '{', '(', ')', '\\', '%', '>', '<', '^', '*', '!', '~', ';', ':', ',', '`', '[', ']', '#', '$', '"', "'", "@"] - switch(host) { + switch (host) { case "vk": case "youtube": url = url.split('&')[0]; @@ -95,7 +95,68 @@ export function unicodeDecode(str) { 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) { + /** @type {PostInfo} */ let def = { vCodec: "h264", vQuality: "720",