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:
parent
ae271fd3c6
commit
cfb05282c3
1 changed files with 39 additions and 60 deletions
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue