added file metadata to videos & fixed youtube dubs
This commit is contained in:
parent
609bf26dd4
commit
2929b9535f
7 changed files with 48 additions and 34 deletions
|
@ -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",
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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`
|
||||||
};
|
};
|
||||||
|
|
|
@ -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()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 = {
|
||||||
|
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",
|
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: 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 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' }
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue