added file metadata to videos & fixed youtube dubs

This commit is contained in:
wukko 2023-08-20 18:14:15 +06:00
parent 609bf26dd4
commit 2929b9535f
7 changed files with 48 additions and 34 deletions

View file

@ -1,7 +1,7 @@
{ {
"name": "cobalt", "name": "cobalt",
"description": "save what you love", "description": "save what you love",
"version": "7.0.1", "version": "7.1",
"author": "wukko", "author": "wukko",
"exports": "./src/cobalt.js", "exports": "./src/cobalt.js",
"type": "module", "type": "module",

View file

@ -9,6 +9,7 @@ export default function(r, host, audioFormat, isAudioOnly, lang, isAudioMuted) {
u: r.urls, u: r.urls,
service: host, service: host,
filename: r.filename, filename: r.filename,
fileMetadata: r.fileMetadata ? r.fileMetadata : false
}, },
params = {} params = {}
@ -28,10 +29,10 @@ export default function(r, host, audioFormat, isAudioOnly, lang, isAudioMuted) {
case "video": case "video":
switch (host) { switch (host) {
case "bilibili": case "bilibili":
params = { type: "render", time: r.time }; params = { type: "render" };
break; break;
case "youtube": case "youtube":
params = { type: r.type, time: r.time }; params = { type: r.type };
break; break;
case "reddit": case "reddit":
responseType = r.typeId; responseType = r.typeId;
@ -139,8 +140,7 @@ export default function(r, host, audioFormat, isAudioOnly, lang, isAudioMuted) {
type: processType, type: processType,
u: Array.isArray(r.urls) ? r.urls[1] : r.urls, u: Array.isArray(r.urls) ? r.urls[1] : r.urls,
audioFormat: audioFormat, audioFormat: audioFormat,
copy: copy, copy: copy
fileMetadata: r.fileMetadata ? r.fileMetadata : false
} }
break; break;
default: default:

View file

@ -21,7 +21,6 @@ export default async function(obj) {
return { return {
urls: [video[0]["baseUrl"], audio[0]["baseUrl"]], urls: [video[0]["baseUrl"], audio[0]["baseUrl"]],
time: streamData.data.timelength,
audioFilename: `bilibili_${obj.id}_audio`, audioFilename: `bilibili_${obj.id}_audio`,
filename: `bilibili_${obj.id}_${video[0]["width"]}x${video[0]["height"]}.mp4` filename: `bilibili_${obj.id}_${video[0]["width"]}x${video[0]["height"]}.mp4`
}; };

View file

@ -1,4 +1,5 @@
import { maxVideoDuration } from "../../config.js"; import { maxVideoDuration } from "../../config.js";
import { cleanString } from "../../sub/utils.js";
let cachedID = {}; let cachedID = {};
@ -72,8 +73,8 @@ export default async function(obj) {
urls: file, urls: file,
audioFilename: `soundcloud_${json.id}`, audioFilename: `soundcloud_${json.id}`,
fileMetadata: { fileMetadata: {
title: json.title, title: cleanString(json.title.replace(/\p{Emoji}/gu, '').trim()),
artist: json.user.username, artist: cleanString(json.user.username.replace(/\p{Emoji}/gu, '').trim()),
} }
} }
} }

View file

@ -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 { cleanString } from '../../sub/utils.js';
const yt = await Innertube.create(); 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] }; if (info.basic_info.duration > maxVideoDuration / 1000) return { error: ['ErrorLengthLimit', maxVideoDuration / 60000] };
let checkBestAudio = (i) => (i["has_audio"] && !i["has_video"]), 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) { 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);
@ -59,24 +60,26 @@ export default async function(o) {
isDubbed = true isDubbed = true
} }
} }
if (hasAudio && o.isAudioOnly) {
let r = { let fileMetadata = {
type: "render", title: cleanString(info.basic_info.title.replace(/\p{Emoji}/gu, '').trim()),
isAudioOnly: true, artist: cleanString(info.basic_info.author.replace("- Topic", "").replace(/\p{Emoji}/gu, '').trim()),
urls: audio.url, }
audioFilename: `youtube_${o.id}_audio${isDubbed ? `_${o.dubLang}`:''}`, if (info.basic_info.short_description && info.basic_info.short_description.startsWith("Provided to YouTube by")) {
fileMetadata: { let descItems = info.basic_info.short_description.split("\n\n");
title: info.basic_info.title, r.fileMetadata.album = descItems[2];
artist: info.basic_info.author.replace("- Topic", "").trim(), r.fileMetadata.copyright = descItems[3];
} if (descItems[4].startsWith("Released on:")) {
}; r.fileMetadata.date = descItems[4].replace("Released on: ", '').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 (hasAudio && o.isAudioOnly) return {
if (descItems[4].startsWith("Released on:")) r.fileMetadata.date = descItems[4].replace("Released on: ", '').trim(); type: "render",
}; isAudioOnly: true,
return r 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)), 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), checkBestVideo = (i) => (i["has_video"] && !i["has_audio"] && qual(i) === bestQuality),
@ -87,7 +90,8 @@ export default async function(o) {
if (single) return { if (single) return {
type: "bridge", type: "bridge",
urls: single.url, 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 { 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: `youtube_${o.id}_${video.width}x${video.height}_${o.format}${isDubbed ? `_${o.dubLang}`:''}.${c[o.format].container}`,
fileMetadata: fileMetadata
}; };
return { error: 'ErrorYTTryOtherCodec' } return { error: 'ErrorYTTryOtherCodec' }

View file

@ -32,7 +32,8 @@ export function streamLiveRender(streamInfo, res) {
if (streamInfo.urls.length !== 2) return fail(res); if (streamInfo.urls.length !== 2) return fail(res);
let audio = got.get(streamInfo.urls[1], { isStream: true }); 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', '-loglevel', '-8',
'-threads', `${getThreads()}`, '-threads', `${getThreads()}`,
'-i', streamInfo.urls[0], '-i', streamInfo.urls[0],
@ -40,8 +41,9 @@ export function streamLiveRender(streamInfo, res) {
'-map', '0:v', '-map', '0:v',
'-map', '1:a', '-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'); args.push('-f', format, 'pipe:4');
let ffmpegProcess = spawn(ffmpeg, args, { let ffmpegProcess = spawn(ffmpeg, args, {
windowsHide: true, windowsHide: true,

View file

@ -1,6 +1,6 @@
import { createStream } from "../stream/manage.js"; import { createStream } from "../stream/manage.js";
let apiVar = { const apiVar = {
allowed: { allowed: {
vCodec: ["h264", "av1", "vp9"], vCodec: ["h264", "av1", "vp9"],
vQuality: ["max", "4320", "2160", "1440", "1080", "720", "480", "360", "240", "144"], vQuality: ["max", "4320", "2160", "1440", "1080", "720", "480", "360", "240", "144"],
@ -8,6 +8,8 @@ let apiVar = {
}, },
booleanOnly: ["isAudioOnly", "isNoTTWatermark", "isTTFullAudio", "isAudioMuted", "dubLang", "vimeoDash"] booleanOnly: ["isAudioOnly", "isNoTTWatermark", "isTTFullAudio", "isAudioMuted", "dubLang", "vimeoDash"]
} }
const forbiddenChars = ['}', '{', '(', ')', '\\', '%', '>', '<', '^', '*', '!', '~', ';', ':', ',', '`', '[', ']', '#', '$', '"', "'", "@", '=='];
const forbiddenCharsString = ['}', '{', '%', '>', '<', '^', ';', '`', '$', '"', "@", '='];
export function apiJSON(type, obj) { export function apiJSON(type, obj) {
try { try {
@ -62,7 +64,6 @@ export function msToTime(d) {
return r; return r;
} }
export function cleanURL(url, host) { export function cleanURL(url, host) {
let forbiddenChars = ['}', '{', '(', ')', '\\', '%', '>', '<', '^', '*', '!', '~', ';', ':', ',', '`', '[', ']', '#', '$', '"', "'", "@", '==']
switch(host) { switch(host) {
case "vk": case "vk":
url = url.includes('clip') ? url.split('&')[0] : url.split('?')[0]; url = url.includes('clip') ? url.split('&')[0] : url.split('?')[0];
@ -88,6 +89,12 @@ export function cleanURL(url, host) {
} }
return url.slice(0, 128) 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) { export function verifyLanguageCode(code) {
return RegExp(/[a-z]{2}/).test(String(code.slice(0, 2).toLowerCase())) ? String(code.slice(0, 2).toLowerCase()) : "en" return RegExp(/[a-z]{2}/).test(String(code.slice(0, 2).toLowerCase())) ? String(code.slice(0, 2).toLowerCase()) : "en"
} }