From 2929b9535f53addb8522f8c7b306ae14a756b855 Mon Sep 17 00:00:00 2001 From: wukko Date: Sun, 20 Aug 2023 18:14:15 +0600 Subject: [PATCH] added file metadata to videos & fixed youtube dubs --- package.json | 2 +- src/modules/processing/matchActionDecider.js | 8 ++-- src/modules/processing/services/bilibili.js | 1 - src/modules/processing/services/soundcloud.js | 5 +- src/modules/processing/services/youtube.js | 47 ++++++++++--------- src/modules/stream/types.js | 8 ++-- src/modules/sub/utils.js | 11 ++++- 7 files changed, 48 insertions(+), 34 deletions(-) diff --git a/package.json b/package.json index 9b8ad05..45ccbf5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cobalt", "description": "save what you love", - "version": "7.0.1", + "version": "7.1", "author": "wukko", "exports": "./src/cobalt.js", "type": "module", diff --git a/src/modules/processing/matchActionDecider.js b/src/modules/processing/matchActionDecider.js index e46991f..262865a 100644 --- a/src/modules/processing/matchActionDecider.js +++ b/src/modules/processing/matchActionDecider.js @@ -9,6 +9,7 @@ export default function(r, host, audioFormat, isAudioOnly, lang, isAudioMuted) { u: r.urls, service: host, filename: r.filename, + fileMetadata: r.fileMetadata ? r.fileMetadata : false }, params = {} @@ -28,10 +29,10 @@ export default function(r, host, audioFormat, isAudioOnly, lang, isAudioMuted) { case "video": switch (host) { case "bilibili": - params = { type: "render", time: r.time }; + params = { type: "render" }; break; case "youtube": - params = { type: r.type, time: r.time }; + params = { type: r.type }; break; case "reddit": responseType = r.typeId; @@ -139,8 +140,7 @@ export default function(r, host, audioFormat, isAudioOnly, lang, isAudioMuted) { type: processType, u: Array.isArray(r.urls) ? r.urls[1] : r.urls, audioFormat: audioFormat, - copy: copy, - fileMetadata: r.fileMetadata ? r.fileMetadata : false + copy: copy } break; default: diff --git a/src/modules/processing/services/bilibili.js b/src/modules/processing/services/bilibili.js index 82964a3..b9690aa 100644 --- a/src/modules/processing/services/bilibili.js +++ b/src/modules/processing/services/bilibili.js @@ -21,7 +21,6 @@ export default async function(obj) { return { urls: [video[0]["baseUrl"], audio[0]["baseUrl"]], - time: streamData.data.timelength, audioFilename: `bilibili_${obj.id}_audio`, filename: `bilibili_${obj.id}_${video[0]["width"]}x${video[0]["height"]}.mp4` }; diff --git a/src/modules/processing/services/soundcloud.js b/src/modules/processing/services/soundcloud.js index 12c2678..2edaad2 100644 --- a/src/modules/processing/services/soundcloud.js +++ b/src/modules/processing/services/soundcloud.js @@ -1,4 +1,5 @@ import { maxVideoDuration } from "../../config.js"; +import { cleanString } from "../../sub/utils.js"; let cachedID = {}; @@ -72,8 +73,8 @@ export default async function(obj) { urls: file, audioFilename: `soundcloud_${json.id}`, fileMetadata: { - title: json.title, - artist: json.user.username, + title: cleanString(json.title.replace(/\p{Emoji}/gu, '').trim()), + artist: cleanString(json.user.username.replace(/\p{Emoji}/gu, '').trim()), } } } diff --git a/src/modules/processing/services/youtube.js b/src/modules/processing/services/youtube.js index e686b4c..445a0db 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 { cleanString } from '../../sub/utils.js'; const yt = await Innertube.create(); @@ -50,7 +51,7 @@ export default async function(o) { if (info.basic_info.duration > maxVideoDuration / 1000) return { error: ['ErrorLengthLimit', maxVideoDuration / 60000] }; let checkBestAudio = (i) => (i["has_audio"] && !i["has_video"]), - audio = adaptive_formats.find(i => checkBestAudio(i) && i["is_original"]); + audio = adaptive_formats.find(i => checkBestAudio(i) && !i["is_dubbed"]); if (o.dubLang) { let dubbedAudio = adaptive_formats.find(i => checkBestAudio(i) && i["language"] === o.dubLang); @@ -59,24 +60,26 @@ export default async function(o) { isDubbed = true } } - if (hasAudio && o.isAudioOnly) { - let r = { - type: "render", - isAudioOnly: true, - urls: audio.url, - audioFilename: `youtube_${o.id}_audio${isDubbed ? `_${o.dubLang}`:''}`, - fileMetadata: { - title: info.basic_info.title, - artist: info.basic_info.author.replace("- Topic", "").trim(), - } - }; - if (info.basic_info.short_description && info.basic_info.short_description.startsWith("Provided to YouTube by")) { - let descItems = info.basic_info.short_description.split("\n\n") - r.fileMetadata.album = descItems[2] - r.fileMetadata.copyright = descItems[3] - if (descItems[4].startsWith("Released on:")) r.fileMetadata.date = descItems[4].replace("Released on: ", '').trim(); - }; - return r + + let fileMetadata = { + title: cleanString(info.basic_info.title.replace(/\p{Emoji}/gu, '').trim()), + artist: cleanString(info.basic_info.author.replace("- Topic", "").replace(/\p{Emoji}/gu, '').trim()), + } + if (info.basic_info.short_description && info.basic_info.short_description.startsWith("Provided to YouTube by")) { + let descItems = info.basic_info.short_description.split("\n\n"); + r.fileMetadata.album = descItems[2]; + r.fileMetadata.copyright = descItems[3]; + if (descItems[4].startsWith("Released on:")) { + r.fileMetadata.date = descItems[4].replace("Released on: ", '').trim() + } + }; + + if (hasAudio && o.isAudioOnly) return { + type: "render", + isAudioOnly: true, + urls: audio.url, + audioFilename: `youtube_${o.id}_audio${isDubbed ? `_${o.dubLang}`:''}`, + fileMetadata: fileMetadata } let checkSingle = (i) => ((qual(i) === quality || qual(i) === bestQuality) && i["mime_type"].includes(c[o.format].codec)), checkBestVideo = (i) => (i["has_video"] && !i["has_audio"] && qual(i) === bestQuality), @@ -87,7 +90,8 @@ export default async function(o) { if (single) return { type: "bridge", urls: single.url, - filename: `youtube_${o.id}_${single.width}x${single.height}_${o.format}.${c[o.format].container}` + filename: `youtube_${o.id}_${single.width}x${single.height}_${o.format}.${c[o.format].container}`, + fileMetadata: fileMetadata } }; @@ -95,7 +99,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: `youtube_${o.id}_${video.width}x${video.height}_${o.format}${isDubbed ? `_${o.dubLang}`:''}.${c[o.format].container}`, + fileMetadata: fileMetadata }; return { error: 'ErrorYTTryOtherCodec' } diff --git a/src/modules/stream/types.js b/src/modules/stream/types.js index dc53405..b527109 100644 --- a/src/modules/stream/types.js +++ b/src/modules/stream/types.js @@ -32,7 +32,8 @@ export function streamLiveRender(streamInfo, res) { if (streamInfo.urls.length !== 2) return fail(res); let audio = got.get(streamInfo.urls[1], { isStream: true }); - let format = streamInfo.filename.split('.')[streamInfo.filename.split('.').length - 1], args = [ + let format = streamInfo.filename.split('.')[streamInfo.filename.split('.').length - 1], + args = [ '-loglevel', '-8', '-threads', `${getThreads()}`, '-i', streamInfo.urls[0], @@ -40,8 +41,9 @@ export function streamLiveRender(streamInfo, res) { '-map', '0:v', '-map', '1:a', ]; - args = args.concat(ffmpegArgs[format]) - if (streamInfo.time) args.push('-t', msToTime(streamInfo.time)); + + args = args.concat(ffmpegArgs[format]); + if (streamInfo.metadata) args = args.concat(metadataManager(streamInfo.metadata)); args.push('-f', format, 'pipe:4'); let ffmpegProcess = spawn(ffmpeg, args, { windowsHide: true, diff --git a/src/modules/sub/utils.js b/src/modules/sub/utils.js index e0e55f6..86a180a 100644 --- a/src/modules/sub/utils.js +++ b/src/modules/sub/utils.js @@ -1,6 +1,6 @@ import { createStream } from "../stream/manage.js"; -let apiVar = { +const apiVar = { allowed: { vCodec: ["h264", "av1", "vp9"], vQuality: ["max", "4320", "2160", "1440", "1080", "720", "480", "360", "240", "144"], @@ -8,6 +8,8 @@ let apiVar = { }, booleanOnly: ["isAudioOnly", "isNoTTWatermark", "isTTFullAudio", "isAudioMuted", "dubLang", "vimeoDash"] } +const forbiddenChars = ['}', '{', '(', ')', '\\', '%', '>', '<', '^', '*', '!', '~', ';', ':', ',', '`', '[', ']', '#', '$', '"', "'", "@", '==']; +const forbiddenCharsString = ['}', '{', '%', '>', '<', '^', ';', '`', '$', '"', "@", '=']; export function apiJSON(type, obj) { try { @@ -62,7 +64,6 @@ export function msToTime(d) { return r; } export function cleanURL(url, host) { - let forbiddenChars = ['}', '{', '(', ')', '\\', '%', '>', '<', '^', '*', '!', '~', ';', ':', ',', '`', '[', ']', '#', '$', '"', "'", "@", '=='] switch(host) { case "vk": url = url.includes('clip') ? url.split('&')[0] : url.split('?')[0]; @@ -88,6 +89,12 @@ export function cleanURL(url, host) { } return url.slice(0, 128) } +export function cleanString(string) { + for (let i in forbiddenCharsString) { + string = string.replaceAll(forbiddenCharsString[i], '') + } + return string; +} export function verifyLanguageCode(code) { return RegExp(/[a-z]{2}/).test(String(code.slice(0, 2).toLowerCase())) ? String(code.slice(0, 2).toLowerCase()) : "en" }