From e6c5201b97b8502539503c02f3f65b1a4cd2e53c Mon Sep 17 00:00:00 2001 From: GSTAR <44748406+GLlgGL@users.noreply.github.com> Date: Tue, 9 Dec 2025 16:19:54 +0100 Subject: [PATCH] Update TurboVidPlay.ts Add height on both direct and proxied. --- src/extractor/TurboVidPlay.ts | 49 +++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/src/extractor/TurboVidPlay.ts b/src/extractor/TurboVidPlay.ts index c7a5c19..f52b2ac 100644 --- a/src/extractor/TurboVidPlay.ts +++ b/src/extractor/TurboVidPlay.ts @@ -43,6 +43,7 @@ export class TurboVidPlay extends Extractor { Referer: url.origin, }; + // ---- 1. Fetch embed HTML ---- const html = await this.fetcher.text(ctx, url, headers); if ( !html @@ -52,7 +53,7 @@ export class TurboVidPlay extends Extractor { throw new NotFoundError(); } - // Extract media URL via named capture group + // ---- 2. Extract media URL via named capture group ---- const match = html.match( /(?:urlPlay|data-hash)\s*=\s*['"](?[^"']+)/, ); @@ -64,14 +65,17 @@ export class TurboVidPlay extends Extractor { throw new NotFoundError('Video link not found'); } - const masterUrl - = mediaUrl.startsWith('//') + // ---- 3. Build master playlist URL ---- + const masterUrl = + mediaUrl.startsWith('//') ? `https:${mediaUrl}` : mediaUrl.startsWith('/') ? url.origin + mediaUrl : mediaUrl; + // ---- 4. Parse playlist → height + /data/ direct stream ---- let directDataStream: string | null = null; + let height: number | undefined; try { const playlistText = await this.fetcher.text( @@ -80,34 +84,44 @@ export class TurboVidPlay extends Extractor { headers, ); - const variantMatches = [ - ...playlistText.matchAll(/#EXT-X-STREAM-INF[^\n]*\n([^\n]+)/g), - ]; + const variants = playlistText.matchAll( + /#EXT-X-STREAM-INF:([^\n]+)\n([^\n]+)/g, + ); - for (const m of variantMatches) { - const variantUrl = m[1]; - if (!variantUrl) continue; + for (const v of variants) { + const attrs = v[1]; + const variantUrl = v[2]; + if (!attrs || !variantUrl) continue; - if (variantUrl.includes('/data/')) { + // Always attempt to extract height (max height wins) + const resMatch = attrs.match(/RESOLUTION=\d+x(\d+)/); + if (resMatch) { + const h = Number(resMatch[1]); + if (!height || h > height) { + height = h; + } + } + + // Decide if direct playback possible (first /data/ wins) + if (!directDataStream && variantUrl.includes('/data/')) { const base = new URL(masterUrl); - - directDataStream - = variantUrl.startsWith('http') + directDataStream = + variantUrl.startsWith('http') ? variantUrl : variantUrl.startsWith('/') ? new URL(variantUrl, base.origin).href : new URL(variantUrl, base.href).href; - - break; } } } catch { - // Ignore playlist parsing errors + // Ignore playlist parsing errors → fallback to MediaFlow only } + // ---- 5. Title ---- const $ = cheerio.load(html); const title = $('title').text().trim() || this.label; + // ---- 6. Direct play if /data/ available ---- if (directDataStream) { return [ { @@ -119,12 +133,14 @@ export class TurboVidPlay extends Extractor { requestHeaders: headers, meta: { ...meta, + height, title, }, }, ]; } + // ---- 7. MediaFlow proxy fallback ---- const proxiedUrl = await buildMediaFlowProxyExtractorStreamUrl( ctx, this.fetcher, @@ -143,6 +159,7 @@ export class TurboVidPlay extends Extractor { requestHeaders: headers, meta: { ...meta, + height, title, }, },