Include turbovid extractor #446

Closed
opened 2025-10-18 20:31:48 +00:00 by GLlgGL · 9 comments
GLlgGL commented 2025-10-18 20:31:48 +00:00 (Migrated from github.com)
Hi There is a turbovidplay extractor here https://github.com/Gujal00/ResolveURL/blob/master/script.module.resolveurl/lib/resolveurl/plugins/turboviplay.py This website has planty of movies on this provider [Movie](https://www.filmaon.bz/filma/inside-furioza/) Links are like this https://emturbovid.com/t/68efee487b1b1 https://turbovidhls.com/t/68efee487b1b1
GLlgGL commented 2025-10-21 10:00:39 +00:00 (Migrated from github.com)

This is being detected as external link but as on the ResolveURL repo does exist a extractor maybe we can bring it to have it playable directly to stremio.

This is being detected as external link but as on the ResolveURL repo does exist a extractor maybe we can bring it to have it playable directly to stremio.
webstreamr commented 2025-10-21 10:22:55 +00:00 (Migrated from github.com)

yes, I agree, looks also easy to do atm since the playlist is seemlingly not ip locked.

→ curl 'https://emturbovid.com/t/68efee487b1b1' -Ls | grep 'data-hash'
    <div id="video_player" data-hash="https://cdn4.turboviplay.com/data/68efee487b1b1/68efee487b1b1.m3u8">

but not a 100% sure, need to take a closer look

yes, I agree, looks also easy to do atm since the playlist is seemlingly not ip locked. ```sh → curl 'https://emturbovid.com/t/68efee487b1b1' -Ls | grep 'data-hash' <div id="video_player" data-hash="https://cdn4.turboviplay.com/data/68efee487b1b1/68efee487b1b1.m3u8"> ``` but not a 100% sure, need to take a closer look
GLlgGL commented 2025-10-21 10:55:47 +00:00 (Migrated from github.com)

`I tried to convert the class from ResolveURL but I failed...

This
https://emturbovid.com/t/68efee487b1b1

Redirects to this
https://turbovidhls.com/t/68efee487b1b1

Than

This
https://cdn.turboviplay.com/data/68efee487b1b1/68efee487b1b1.m3u8

Gives this
#EXTM3U
#EXT-X-VERSION:6
#EXT-X-STREAM-INF:BANDWIDTH=52800,RESOLUTION=854x480,CODECS="avc1.64001e,mp4a.40.2"
https://cdn156.saznever.com/data/F/DA/68efee487b1b1/68efee487b1b1480.m3u8`

`I tried to convert the class from ResolveURL but I failed... This https://emturbovid.com/t/68efee487b1b1 Redirects to this https://turbovidhls.com/t/68efee487b1b1 Than This https://cdn.turboviplay.com/data/68efee487b1b1/68efee487b1b1.m3u8 Gives this #EXTM3U #EXT-X-VERSION:6 #EXT-X-STREAM-INF:BANDWIDTH=52800,RESOLUTION=854x480,CODECS="avc1.64001e,mp4a.40.2" https://cdn156.saznever.com/data/F/DA/68efee487b1b1/68efee487b1b1480.m3u8`
webstreamr commented 2025-10-21 11:28:12 +00:00 (Migrated from github.com)

might need a referer to access it, you can play around with curl on the terminal if you have one. e.g. curl <URL> -e 'https://emturbovid.com'. I tried briefly and got 403 otherwise

might need a referer to access it, you can play around with curl on the terminal if you have one. e.g. `curl <URL> -e 'https://emturbovid.com'`. I tried briefly and got 403 otherwise
GLlgGL commented 2025-10-21 12:46:52 +00:00 (Migrated from github.com)
Image

This from my local host but don't know from vercel

<img width="1032" height="110" alt="Image" src="https://github.com/user-attachments/assets/0d036e7b-5095-413a-be96-0de66314192d" /> This from my local host but don't know from vercel
GLlgGL commented 2025-10-28 11:06:39 +00:00 (Migrated from github.com)

This code shows the file source on stremio, it can play but appears only stuck and black...

If you move the seconds for ex. from 00:00:00 to 00:02:00 it goes there but still stays black...

What I'm missing?

import * as cheerio from 'cheerio';
import { NotFoundError } from '../error';
import { Context, Format, Meta, UrlResult } from '../types';
import { Extractor } from './Extractor';
import { guessHeightFromPlaylist } from '../utils/height';

export class TurboVIPlay extends Extractor {
public readonly id = 'turboviplay';
public readonly label = 'TurboVIPlay';
public override readonly ttl: number = 10800000; // 3h

private domains = [
'turboviplay.com',
'emturbovid.com',
'tuborstb.co',
'javggvideo.xyz',
'stbturbo.xyz',
'turbovidhls.com',
];

public supports(_ctx: Context, url: URL): boolean {
return this.domains.some((d) => url.host.includes(d));
}

public override normalize(url: URL): URL {
// Always use /t/{id} pattern now
return new URL(url.href.replace(//(e|d|embed-)//, '/t/'));
}

protected async extractInternal(ctx: Context, url: URL, meta: Meta): Promise<UrlResult[]> {
// Force correct referer for all requests
const headers = {
Referer: 'https://turbovidhls.com',
};

// Fetch main page
const html = await this.fetcher.text(ctx, url, { headers });

if (!html || html.includes('File Not Found') || html.includes('Pending in queue')) {
  throw new NotFoundError();
}

// Extract playable link
const match = html.match(/(?:urlPlay|data-hash)\s*=\s*['"](?<url>[^"']+)/);
const mediaUrl = match?.groups?.['url'];
if (!mediaUrl) {
  throw new NotFoundError('Video link not found');
}

// Normalize relative/protocol-less URL
let finalUrl = mediaUrl;
if (finalUrl.startsWith('//')) finalUrl = 'https:' + finalUrl;
else if (finalUrl.startsWith('/')) finalUrl = url.origin + finalUrl;

// Follow redirects if any
const resolvedUrl = await this.followRedirect(ctx, finalUrl, headers);

// If it's a master playlist, pick the best variant
const playableUrl = await this.resolveHlsMaster(ctx, resolvedUrl, headers);

// Try to guess video height
let height: number | undefined;
try {
  height = await guessHeightFromPlaylist(ctx, this.fetcher, new URL(playableUrl), { headers });
} catch {
  height = undefined;
}

// Extract title
const $ = cheerio.load(html);
const title = $('title').text().trim() || this.label;

// Return result
return [
  {
    url: new URL(playableUrl),
    format: playableUrl.endsWith('.m3u8') ? Format.hls : Format.mp4,
    label: this.label,
    sourceId: `${this.id}_${meta.countryCodes?.join('_') ?? 'all'}`,
    ttl: this.ttl,
    meta: {
      ...meta,
      title,
      height,
      referer: 'https://turbovidhls.com', // Ensure player uses correct referer
    },
  },
];

}

/** Follow redirects manually — like helpers.get_media_url() in Gujal’s Python plugin */
private async followRedirect(ctx: Context, link: string, headers: Record<string, string>): Promise {
try {
const resp = await this.fetcher.head(ctx, new URL(link), { headers, redirect: 'follow' });
if (resp && typeof resp['url'] === 'string') return String(resp['url']);
} catch {
try {
const resp = await this.fetcher.fetch(ctx, new URL(link), { headers, redirect: 'follow' });
if (resp && typeof resp['url'] === 'string') return String(resp['url']);
} catch {
// ignore
}
}
return link;
}

/** Resolve HLS master playlist to highest resolution variant */
private async resolveHlsMaster(ctx: Context, url: string, headers: Record<string, string>): Promise {
const text = await this.fetcher.text(ctx, new URL(url), { headers });
if (!text || !text.includes('#EXT-X-STREAM-INF')) return url;

const matches = Array.from(
  text.matchAll(/#EXT-X-STREAM-INF:.*RESOLUTION=(\d+)x(\d+)[^\n]*\n([^\n]+)/g)
);

if (matches.length === 0) return url;

// Sort by resolution height (descending)
matches.sort((a, b) => parseInt(b[2] ?? '0', 10) - parseInt(a[2] ?? '0', 10));

const best = matches[0]?.[3];
if (!best) return url;

const base = new URL(url);
if (best.startsWith('http')) return best;
if (best.startsWith('/')) return new URL(best, base.origin).href;
return new URL(best, base.href).href;

}
}

This code shows the file source on stremio, it can play but appears only stuck and black... If you move the seconds for ex. from 00:00:00 to 00:02:00 it goes there but still stays black... What I'm missing? import * as cheerio from 'cheerio'; import { NotFoundError } from '../error'; import { Context, Format, Meta, UrlResult } from '../types'; import { Extractor } from './Extractor'; import { guessHeightFromPlaylist } from '../utils/height'; export class TurboVIPlay extends Extractor { public readonly id = 'turboviplay'; public readonly label = 'TurboVIPlay'; public override readonly ttl: number = 10800000; // 3h private domains = [ 'turboviplay.com', 'emturbovid.com', 'tuborstb.co', 'javggvideo.xyz', 'stbturbo.xyz', 'turbovidhls.com', ]; public supports(_ctx: Context, url: URL): boolean { return this.domains.some((d) => url.host.includes(d)); } public override normalize(url: URL): URL { // Always use /t/{id} pattern now return new URL(url.href.replace(/\/(e|d|embed-)\//, '/t/')); } protected async extractInternal(ctx: Context, url: URL, meta: Meta): Promise<UrlResult[]> { // Force correct referer for all requests const headers = { Referer: 'https://turbovidhls.com', }; // Fetch main page const html = await this.fetcher.text(ctx, url, { headers }); if (!html || html.includes('File Not Found') || html.includes('Pending in queue')) { throw new NotFoundError(); } // Extract playable link const match = html.match(/(?:urlPlay|data-hash)\s*=\s*['"](?<url>[^"']+)/); const mediaUrl = match?.groups?.['url']; if (!mediaUrl) { throw new NotFoundError('Video link not found'); } // Normalize relative/protocol-less URL let finalUrl = mediaUrl; if (finalUrl.startsWith('//')) finalUrl = 'https:' + finalUrl; else if (finalUrl.startsWith('/')) finalUrl = url.origin + finalUrl; // Follow redirects if any const resolvedUrl = await this.followRedirect(ctx, finalUrl, headers); // If it's a master playlist, pick the best variant const playableUrl = await this.resolveHlsMaster(ctx, resolvedUrl, headers); // Try to guess video height let height: number | undefined; try { height = await guessHeightFromPlaylist(ctx, this.fetcher, new URL(playableUrl), { headers }); } catch { height = undefined; } // Extract title const $ = cheerio.load(html); const title = $('title').text().trim() || this.label; // Return result return [ { url: new URL(playableUrl), format: playableUrl.endsWith('.m3u8') ? Format.hls : Format.mp4, label: this.label, sourceId: `${this.id}_${meta.countryCodes?.join('_') ?? 'all'}`, ttl: this.ttl, meta: { ...meta, title, height, referer: 'https://turbovidhls.com', // Ensure player uses correct referer }, }, ]; } /** Follow redirects manually — like helpers.get_media_url() in Gujal’s Python plugin */ private async followRedirect(ctx: Context, link: string, headers: Record<string, string>): Promise<string> { try { const resp = await this.fetcher.head(ctx, new URL(link), { headers, redirect: 'follow' }); if (resp && typeof resp['url'] === 'string') return String(resp['url']); } catch { try { const resp = await this.fetcher.fetch(ctx, new URL(link), { headers, redirect: 'follow' }); if (resp && typeof resp['url'] === 'string') return String(resp['url']); } catch { // ignore } } return link; } /** Resolve HLS master playlist to highest resolution variant */ private async resolveHlsMaster(ctx: Context, url: string, headers: Record<string, string>): Promise<string> { const text = await this.fetcher.text(ctx, new URL(url), { headers }); if (!text || !text.includes('#EXT-X-STREAM-INF')) return url; const matches = Array.from( text.matchAll(/#EXT-X-STREAM-INF:.*RESOLUTION=(\d+)x(\d+)[^\n]*\n([^\n]+)/g) ); if (matches.length === 0) return url; // Sort by resolution height (descending) matches.sort((a, b) => parseInt(b[2] ?? '0', 10) - parseInt(a[2] ?? '0', 10)); const best = matches[0]?.[3]; if (!best) return url; const base = new URL(url); if (best.startsWith('http')) return best; if (best.startsWith('/')) return new URL(best, base.origin).href; return new URL(best, base.href).href; } }
GLlgGL commented 2025-10-28 11:31:23 +00:00 (Migrated from github.com)

2025-10-28T11:23:32.031Z info 7034ba89-dfc6-4eb8-bcd1-26350afa9487: Fetch HEAD https://cdn1.turboviplay.com/data/68f73bcb1aeb9/68f73bcb1aeb9.m3u8 with referer https://www.filmaon.bz/filma/a-royal-montana-christmas/

it is able to get the final m3u8 playable link but shows black

2025-10-28T11:23:32.031Z info 7034ba89-dfc6-4eb8-bcd1-26350afa9487: Fetch HEAD https://cdn1.turboviplay.com/data/68f73bcb1aeb9/68f73bcb1aeb9.m3u8 with referer https://www.filmaon.bz/filma/a-royal-montana-christmas/ it is able to get the final m3u8 playable link but shows black
GLlgGL commented 2025-10-28 11:50:22 +00:00 (Migrated from github.com)

When you test the link on a playlist directly to vlc it works

#EXTM3U
#EXTVLCOPT:http-referrer=https://turbovidhls.com
https://cdn154.foudsea.com/data/E/B9/68f73bcb1aeb9/68f73bcb1aeb9720.m3u8

What is wrong on the code

hmmm...

maybe because the master m3u8 inside is like this

#EXTM3U
#EXT-X-VERSION:6
#EXT-X-STREAM-INF:BANDWIDTH=52800,RESOLUTION=854x480,CODECS="avc1.64001e,mp4a.40.2"
https://cdn151.teifanc.com/data/B/F1/68f73bcb1aeb9/68f73bcb1aeb9480.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=105600,RESOLUTION=1280x720,CODECS="avc1.64001f,mp4a.40.2"
https://cdn154.foudsea.com/data/E/B9/68f73bcb1aeb9/68f73bcb1aeb9720.m3u8

When you test the link on a playlist directly to vlc it works #EXTM3U #EXTVLCOPT:http-referrer=https://turbovidhls.com https://cdn154.foudsea.com/data/E/B9/68f73bcb1aeb9/68f73bcb1aeb9720.m3u8 What is wrong on the code hmmm... maybe because the master m3u8 inside is like this #EXTM3U #EXT-X-VERSION:6 #EXT-X-STREAM-INF:BANDWIDTH=52800,RESOLUTION=854x480,CODECS="avc1.64001e,mp4a.40.2" https://cdn151.teifanc.com/data/B/F1/68f73bcb1aeb9/68f73bcb1aeb9480.m3u8 #EXT-X-STREAM-INF:BANDWIDTH=105600,RESOLUTION=1280x720,CODECS="avc1.64001f,mp4a.40.2" https://cdn154.foudsea.com/data/E/B9/68f73bcb1aeb9/68f73bcb1aeb9720.m3u8
GLlgGL commented 2025-11-25 23:40:20 +00:00 (Migrated from github.com)

I was able to solve this. The turbovidplay hase two type of links:

  1. Directly accesible and playble by code that exist in websteamr
  2. Links that are hiden with fake png which needs support of mediaflow and a patch I already pull requested which strips fake bytes for different providers which are using same technique.

I also have the code for webstreamr addon for you to add later which supports both type of links by checking if /data/ exist in the master link, do directly decoding, if not than pass it to mediaflow to strip those fake bytes and get ir back playble.

When he merges I will open here a pull request for you to add turbovidplay support.

https://github.com/mhdzumair/mediaflow-proxy/pull/176#pullrequestreview-3507369944

I was able to solve this. The turbovidplay hase two type of links: 1. Directly accesible and playble by code that exist in websteamr 2. Links that are hiden with fake png which needs support of mediaflow and a patch I already pull requested which strips fake bytes for different providers which are using same technique. I also have the code for webstreamr addon for you to add later which supports both type of links by checking if /data/ exist in the master link, do directly decoding, if not than pass it to mediaflow to strip those fake bytes and get ir back playble. When he merges I will open here a pull request for you to add turbovidplay support. https://github.com/mhdzumair/mediaflow-proxy/pull/176#pullrequestreview-3507369944
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: Creepso/webstreamr-github#446
No description provided.