api/youtube/adaptive: refactor, avoid extra loops, fallback all codecs

This commit is contained in:
wukko 2024-11-13 18:41:57 +06:00
parent c05f40b279
commit c88e21d4a8
No known key found for this signature in database
GPG key ID: 3E30B3F26C7B4AA2

View file

@ -215,7 +215,7 @@ export default async function(o) {
} }
let video, audio, dubbedLanguage, let video, audio, dubbedLanguage,
format = o.format || "h264"; codec = o.format || "h264";
if (useHLS) { if (useHLS) {
const hlsManifest = info.streaming_data.hls_manifest_url; const hlsManifest = info.streaming_data.hls_manifest_url;
@ -247,7 +247,7 @@ export default async function(o) {
} }
const matchHlsCodec = codecs => ( const matchHlsCodec = codecs => (
codecs.includes(hlsCodecList[format].videoCodec) codecs.includes(hlsCodecList[codec].videoCodec)
); );
const best = variants.find(i => matchHlsCodec(i.codecs)); const best = variants.find(i => matchHlsCodec(i.codecs));
@ -259,7 +259,7 @@ export default async function(o) {
let selected = preferred || best; let selected = preferred || best;
if (!selected) { if (!selected) {
format = "h264"; codec = "h264";
selected = variants.find(i => matchHlsCodec(i.codecs)); selected = variants.find(i => matchHlsCodec(i.codecs));
} }
@ -290,51 +290,83 @@ export default async function(o) {
selected.subtitles = []; selected.subtitles = [];
video = selected; video = selected;
} else { } else {
let fallback = false; // i miss typescript so bad
const sorted_formats = {
const filterByCodec = (formats) => h264: {
formats.filter(e => video: [],
e.mime_type.includes(codecList[format].videoCodec) audio: [],
|| e.mime_type.includes(codecList[format].audioCodec) bestVideo: undefined,
).sort((a, b) => bestAudio: undefined,
Number(b.bitrate) - Number(a.bitrate) },
); vp9: {
video: [],
let adaptive_formats = filterByCodec(info.streaming_data.adaptive_formats); audio: [],
bestVideo: undefined,
const checkBestVideo = (i) => (i.has_video && i.content_length); bestAudio: undefined,
const checkBestAudio = (i) => (i.has_audio && i.content_length); },
const checkNoMedia = (vid, aud) => (!vid && !o.isAudioOnly) || (!aud && o.isAudioOnly); av1: {
video: [],
const earlyBestVideo = adaptive_formats.find(i => checkBestVideo(i)); audio: [],
const earlyBestAudio = adaptive_formats.find(i => checkBestAudio(i)); bestVideo: undefined,
bestAudio: undefined,
// check if formats have all needed media and fall back to h264 if not },
if (["vp9", "av1"].includes(format) && checkNoMedia(earlyBestVideo, earlyBestAudio)) {
fallback = true;
format = "h264";
adaptive_formats = filterByCodec(info.streaming_data.adaptive_formats);
} }
const bestVideo = !fallback ? earlyBestVideo : adaptive_formats.find(i => checkBestVideo(i)); const checkFormat = (format, pCodec) => format.content_length &&
const bestAudio = !fallback ? earlyBestAudio : adaptive_formats.find(i => checkBestAudio(i)); (format.mime_type.includes(codecList[pCodec].videoCodec)
|| format.mime_type.includes(codecList[pCodec].audioCodec));
if (checkNoMedia(bestVideo, bestAudio)) { // sort formats & weed out bad ones
info.streaming_data.adaptive_formats.sort((a, b) =>
Number(b.bitrate) - Number(a.bitrate)
).forEach(format => {
Object.keys(codecList).forEach(yCodec => {
const sorted = sorted_formats[yCodec];
const goodFormat = checkFormat(format, yCodec);
if (!goodFormat) return;
if (format.has_video) {
sorted.video.push(format);
if (!sorted.bestVideo) sorted.bestVideo = format;
}
if (format.has_audio) {
sorted.audio.push(format);
if (!sorted.bestAudio) sorted.bestAudio = format;
}
})
});
const noBestMedia = () => {
const vid = sorted_formats[codec]?.bestVideo;
const aud = sorted_formats[codec]?.bestAudio;
return (!vid && !o.isAudioOnly) || (!aud && o.isAudioOnly)
};
if (noBestMedia()) {
if (codec === "av1") codec = "vp9";
if (codec === "vp9") codec = "av1";
// if there's no higher quality fallback, then use h264
if (noBestMedia()) codec = "h264";
}
// if there's no proper combo of av1, vp9, or h264, then give up
if (noBestMedia()) {
return { error: "youtube.no_matching_format" }; return { error: "youtube.no_matching_format" };
} }
audio = bestAudio; audio = sorted_formats[codec].bestAudio;
if (audio?.audio_track && !audio?.audio_track?.audio_is_default) { if (audio?.audio_track && !audio?.audio_track?.audio_is_default) {
audio = adaptive_formats.find(i => audio = sorted_formats[codec].audio.find(i =>
checkBestAudio(i) && i?.audio_track?.audio_is_default i?.audio_track?.audio_is_default
); );
} }
if (o.dubLang) { if (o.dubLang) {
const dubbedAudio = adaptive_formats.find(i => const dubbedAudio = sorted_formats[codec].audio.find(i =>
checkBestAudio(i) && i.language?.startsWith(o.dubLang) && i.audio_track i.language?.startsWith(o.dubLang) && i.audio_track
) );
if (dubbedAudio && !dubbedAudio?.audio_track?.audio_is_default) { if (dubbedAudio && !dubbedAudio?.audio_track?.audio_is_default) {
audio = dubbedAudio; audio = dubbedAudio;
@ -350,14 +382,14 @@ export default async function(o) {
}) })
} }
const bestQuality = qual(bestVideo); const bestQuality = qual(sorted_formats[codec].bestVideo);
const useBestQuality = quality >= bestQuality; const useBestQuality = quality >= bestQuality;
video = useBestQuality ? bestVideo : adaptive_formats.find(i => video = useBestQuality
qual(i) === quality && checkBestVideo(i) ? sorted_formats[codec].bestVideo
); : sorted_formats[codec].video.find(i => qual(i) === quality);
if (!video) video = bestVideo; if (!video) video = sorted_formats[codec].bestVideo;
} }
} }
@ -387,7 +419,7 @@ export default async function(o) {
} }
if (audio && o.isAudioOnly) { if (audio && o.isAudioOnly) {
let bestAudio = format === "h264" ? "m4a" : "opus"; let bestAudio = codec === "h264" ? "m4a" : "opus";
let urls = audio.url; let urls = audio.url;
if (useHLS) { if (useHLS) {
@ -412,7 +444,7 @@ export default async function(o) {
if (useHLS) { if (useHLS) {
resolution = normalizeQuality(video.resolution); resolution = normalizeQuality(video.resolution);
filenameAttributes.resolution = `${video.resolution.width}x${video.resolution.height}`; filenameAttributes.resolution = `${video.resolution.width}x${video.resolution.height}`;
filenameAttributes.extension = hlsCodecList[format].container; filenameAttributes.extension = hlsCodecList[codec].container;
video = video.uri; video = video.uri;
audio = audio.uri; audio = audio.uri;
@ -422,14 +454,14 @@ export default async function(o) {
height: video.height, height: video.height,
}); });
filenameAttributes.resolution = `${video.width}x${video.height}`; filenameAttributes.resolution = `${video.width}x${video.height}`;
filenameAttributes.extension = codecList[format].container; filenameAttributes.extension = codecList[codec].container;
video = video.url; video = video.url;
audio = audio.url; audio = audio.url;
} }
filenameAttributes.qualityLabel = `${resolution}p`; filenameAttributes.qualityLabel = `${resolution}p`;
filenameAttributes.youtubeFormat = format; filenameAttributes.youtubeFormat = codec;
return { return {
type: "merge", type: "merge",