1
0
Fork 0
mirror of https://github.com/wukko/cobalt.git synced 2025-04-02 08:01:38 +02:00

api/youtube: refactor, fallback codecs, don't return premuxed videos

This commit is contained in:
wukko 2024-10-23 19:56:59 +06:00
parent ae271fd3c6
commit cfb05282c3
No known key found for this signature in database
GPG key ID: 3E30B3F26C7B4AA2

View file

@ -9,7 +9,7 @@ const PLAYER_REFRESH_PERIOD = 1000 * 60 * 15; // ms
let innertube, lastRefreshedAt; let innertube, lastRefreshedAt;
const codecMatch = { const codecList = {
h264: { h264: {
videoCodec: "avc1", videoCodec: "avc1",
audioCodec: "mp4a", audioCodec: "mp4a",
@ -120,12 +120,12 @@ export default async function(o) {
let info, isDubbed, let info, isDubbed,
format = o.format || "h264"; format = o.format || "h264";
function qual(i) { const qual = (i) => {
if (!i.quality_label) { if (!i.quality_label) {
return; return;
} }
return i.quality_label.split('p')[0].split('s')[0] return i.quality_label.split('p', 2)[0].split('s', 2)[0]
} }
try { try {
@ -198,36 +198,31 @@ export default async function(o) {
} }
const filterByCodec = (formats) => const filterByCodec = (formats) =>
formats formats.filter(e =>
.filter(e => e.mime_type.includes(codecList[format].videoCodec)
e.mime_type.includes(codecMatch[format].videoCodec) || e.mime_type.includes(codecList[format].audioCodec)
|| e.mime_type.includes(codecMatch[format].audioCodec) ).sort((a, b) =>
) Number(b.bitrate) - Number(a.bitrate)
.sort((a, b) => Number(b.bitrate) - Number(a.bitrate)); );
let adaptive_formats = filterByCodec(info.streaming_data.adaptive_formats); let adaptive_formats = filterByCodec(info.streaming_data.adaptive_formats);
if (adaptive_formats.length === 0 && format === "vp9") { if (adaptive_formats.length === 0 && ["vp9", "av1"].includes(format)) {
format = "h264" format = "h264";
adaptive_formats = filterByCodec(info.streaming_data.adaptive_formats) adaptive_formats = filterByCodec(info.streaming_data.adaptive_formats);
} }
let bestQuality;
const bestVideo = adaptive_formats.find(i => i.has_video && i.content_length); const bestVideo = adaptive_formats.find(i => i.has_video && i.content_length);
const hasAudio = adaptive_formats.find(i => i.has_audio && i.content_length); const hasAudio = adaptive_formats.find(i => i.has_audio && i.content_length);
if (bestVideo) bestQuality = qual(bestVideo); if (!bestVideo || (!hasAudio && o.isAudioOnly)) {
return { error: "fetch.empty" };
if ((!bestQuality && !o.isAudioOnly) || !hasAudio) {
return { error: "youtube.codec" };
} }
const bestQuality = qual(bestVideo);
const checkBestAudio = (i) => (i.has_audio && !i.has_video); const checkBestAudio = (i) => (i.has_audio && !i.has_video);
let audio = adaptive_formats.find(i => let audio = adaptive_formats.find(i => checkBestAudio(i) && i.is_original);
checkBestAudio(i) && i.is_original
);
if (o.dubLang) { if (o.dubLang) {
let dubbedAudio = adaptive_formats.find(i => let dubbedAudio = adaptive_formats.find(i =>
@ -244,13 +239,14 @@ export default async function(o) {
audio = adaptive_formats.find(i => checkBestAudio(i)); audio = adaptive_formats.find(i => checkBestAudio(i));
} }
let fileMetadata = { const fileMetadata = {
title: cleanString(basicInfo.title.trim()), title: cleanString(basicInfo.title.trim()),
artist: cleanString(basicInfo.author.replace("- Topic", "").trim()), artist: cleanString(basicInfo.author.replace("- Topic", "").trim())
} }
if (basicInfo?.short_description?.startsWith("Provided to YouTube by")) { if (basicInfo?.short_description?.startsWith("Provided to YouTube by")) {
let descItems = basicInfo.short_description.split("\n\n", 5); const descItems = basicInfo.short_description.split("\n\n", 5);
if (descItems.length === 5) { if (descItems.length === 5) {
fileMetadata.album = descItems[2]; fileMetadata.album = descItems[2];
fileMetadata.copyright = descItems[3]; fileMetadata.copyright = descItems[3];
@ -260,7 +256,7 @@ export default async function(o) {
} }
} }
let filenameAttributes = { const filenameAttributes = {
service: "youtube", service: "youtube",
id: o.id, id: o.id,
title: fileMetadata.title, title: fileMetadata.title,
@ -271,46 +267,29 @@ export default async function(o) {
if (audio && o.isAudioOnly) return { if (audio && o.isAudioOnly) return {
type: "audio", type: "audio",
isAudioOnly: true, isAudioOnly: true,
urls: audio.decipher(yt.session.player), urls: audio.url,
filenameAttributes: filenameAttributes, filenameAttributes,
fileMetadata: fileMetadata, fileMetadata,
bestAudio: format === "h264" ? "m4a" : "opus" bestAudio: format === "h264" ? "m4a" : "opus",
} }
const matchingQuality = Number(quality) > Number(bestQuality) ? bestQuality : quality, const matchingQuality = Number(quality) > Number(bestQuality) ? bestQuality : quality;
checkSingle = i => const video = adaptive_formats.find(i =>
qual(i) === matchingQuality && i.mime_type.includes(codecMatch[format].videoCodec), qual(i) === matchingQuality && i.has_video && !i.has_audio
checkRender = i => );
qual(i) === matchingQuality && i.has_video && !i.has_audio;
let match, type, urls; if (video && audio) {
filenameAttributes.qualityLabel = video.quality_label;
// prefer good premuxed videos if available filenameAttributes.resolution = `${video.width}x${video.height}`;
if (!o.isAudioOnly && !o.isAudioMuted && format === "h264" && bestVideo.fps <= 30) { filenameAttributes.extension = codecList[format].container;
match = info.streaming_data.formats.find(checkSingle);
type = "proxy";
urls = match?.decipher(yt.session.player);
}
const video = adaptive_formats.find(checkRender);
if (!match && video && audio) {
match = video;
type = "merge";
urls = [
video.decipher(yt.session.player),
audio.decipher(yt.session.player)
]
}
if (match) {
filenameAttributes.qualityLabel = match.quality_label;
filenameAttributes.resolution = `${match.width}x${match.height}`;
filenameAttributes.extension = codecMatch[format].container;
filenameAttributes.youtubeFormat = format; filenameAttributes.youtubeFormat = format;
return { return {
type, type: "merge",
urls, urls: [
video.url,
audio.url
],
filenameAttributes, filenameAttributes,
fileMetadata fileMetadata
} }