feat(miruro): attempt to retrieve quality from stream playlist
This commit is contained in:
1 parent
6e86adb0e6
commit
27a87191c3
7 files changed
+928
-42
No files matched your search
+80
-15
@@ -2,10 +2,11 @@
|
||||
* Resolve TMDB media to AniList, then retrieve Miruro episode sources.
|
||||
*/
|
||||
|
||||
import { BASE_URL, fetchJson, HEADERS, secureGet } from './http.js';
|
||||
import { BASE_URL, fetchJson, fetchText, HEADERS, secureGet } from './http.js';
|
||||
|
||||
const ANIMAP_URL = 'https://animap.id/api/map';
|
||||
const ANILIST_URL = 'https://graphql.anilist.co';
|
||||
const HLS_QUALITY_TIMEOUT_MS = 4000;
|
||||
|
||||
// Miruro's visible native-player providers and their preferred subtitle mode.
|
||||
const PROVIDERS = [
|
||||
@@ -270,15 +271,71 @@ function getOrigin(url) {
|
||||
return match ? match[1] : BASE_URL;
|
||||
}
|
||||
|
||||
function getQuality(stream) {
|
||||
const value = String(stream.quality || stream.server || 'HD');
|
||||
function getDeclaredQuality(stream) {
|
||||
const value = `${stream.quality || ''} ${stream.resolution || ''} ${stream.server || ''}`;
|
||||
const resolution = value.match(/(2160|1440|1080|720|480|360)p?/i);
|
||||
|
||||
if (resolution) {
|
||||
return `${resolution[1]}p`;
|
||||
}
|
||||
|
||||
return /4k/i.test(value) ? '4K' : 'HD';
|
||||
return /4k/i.test(value) ? '4K' : null;
|
||||
}
|
||||
|
||||
function getHlsQuality(manifest) {
|
||||
const heights = [];
|
||||
const pattern = /\bRESOLUTION\s*=\s*\d+\s*x\s*(\d+)\b/gi;
|
||||
let match;
|
||||
|
||||
while ((match = pattern.exec(manifest)) !== null) {
|
||||
const height = Number(match[1]);
|
||||
|
||||
if (height > 0) {
|
||||
heights.push(height);
|
||||
}
|
||||
}
|
||||
|
||||
return heights.length > 0 ? `${Math.max(...heights)}p` : null;
|
||||
}
|
||||
|
||||
function isHlsStream(stream) {
|
||||
const type = String(stream && stream.type || '').toLowerCase();
|
||||
return type === 'hls' || type === 'm3u8' || type === 'application/x-mpegurl';
|
||||
}
|
||||
|
||||
async function resolveQuality(stream, headers) {
|
||||
const declaredQuality = getDeclaredQuality(stream);
|
||||
|
||||
if (declaredQuality) {
|
||||
return declaredQuality;
|
||||
}
|
||||
|
||||
if (isHlsStream(stream)) {
|
||||
let timeoutId;
|
||||
|
||||
try {
|
||||
const manifest = await Promise.race([
|
||||
fetchText(stream.url, { headers }),
|
||||
new Promise((resolve) => {
|
||||
timeoutId = setTimeout(() => resolve(null), HLS_QUALITY_TIMEOUT_MS);
|
||||
})
|
||||
]);
|
||||
const hlsQuality = typeof manifest === 'string' ? getHlsQuality(manifest) : null;
|
||||
|
||||
if (hlsQuality) {
|
||||
return hlsQuality;
|
||||
}
|
||||
} catch (_) {
|
||||
// A stream can still be playable when its manifest rejects this
|
||||
// metadata request, so retain it with an unknown HD quality.
|
||||
} finally {
|
||||
if (timeoutId != null) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 'HD';
|
||||
}
|
||||
|
||||
function isPlayableStream(stream) {
|
||||
@@ -291,14 +348,26 @@ function isPlayableStream(stream) {
|
||||
);
|
||||
}
|
||||
|
||||
function normalizeStreams(sourcePayload, provider, variant) {
|
||||
async function normalizeStreams(sourcePayload, provider, variant) {
|
||||
const subtitleCount = Array.isArray(sourcePayload.subtitles) ? sourcePayload.subtitles.length : 0;
|
||||
const language = variant === 'dub' ? 'DUB' : 'SUB';
|
||||
|
||||
return (sourcePayload.streams || []).filter(isPlayableStream).map((stream) => {
|
||||
const referer = stream.referer || BASE_URL;
|
||||
const server = stream.server || getQuality(stream);
|
||||
return Promise.all((sourcePayload.streams || []).filter(isPlayableStream).map(async (stream) => {
|
||||
const streamHeaders = stream.headers && typeof stream.headers === 'object'
|
||||
? stream.headers
|
||||
: {};
|
||||
const referer = stream.referer || streamHeaders.Referer || streamHeaders.referer || BASE_URL;
|
||||
const subtitleLabel = subtitleCount > 0 && variant !== 'dub' ? ' · CC' : '';
|
||||
const headers = {
|
||||
...streamHeaders,
|
||||
'Referer': referer,
|
||||
'Origin': getOrigin(referer),
|
||||
'User-Agent': HEADERS['User-Agent']
|
||||
};
|
||||
const quality = await resolveQuality(stream, headers);
|
||||
const server = !stream.server || /^(?:HD|auto)$/i.test(stream.server)
|
||||
? quality
|
||||
: stream.server;
|
||||
|
||||
return {
|
||||
// Nuvio's source picker prominently displays `name`, so include the
|
||||
@@ -306,14 +375,10 @@ function normalizeStreams(sourcePayload, provider, variant) {
|
||||
name: `Miruro · ${provider.label} · ${language}`,
|
||||
title: `[${language}] ${provider.label} · ${server}${subtitleLabel}`,
|
||||
url: stream.url,
|
||||
quality: getQuality(stream),
|
||||
headers: {
|
||||
'Referer': referer,
|
||||
'Origin': getOrigin(referer),
|
||||
'User-Agent': HEADERS['User-Agent']
|
||||
}
|
||||
quality,
|
||||
headers
|
||||
};
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
async function getProviderStreams(provider, providerPayload, anilistId, episodeNumber) {
|
||||
|
||||
Reference in new issue
Block a user