Allow embeds and videos to return multiple streams + add identifiers to list returns

This commit is contained in:
mrjvs 2023-12-24 19:46:12 +01:00
parent d44320e362
commit 0affe83d24
21 changed files with 142 additions and 90 deletions

View File

@ -1,9 +1,9 @@
import { mockEmbeds, mockSources } from '@/__test__/providerTests'; import { mockEmbeds, mockSources } from '@/__test__/providerTests';
import { FeatureMap } from '@/main/targets.ts'; import { FeatureMap } from '@/main/targets';
import { getProviders } from '@/providers/get'; import { getProviders } from '@/providers/get';
import { vi, describe, it, expect, afterEach } from 'vitest'; import { vi, describe, it, expect, afterEach } from 'vitest';
const mocks = await vi.hoisted(async () => (await import('../providerTests.ts')).makeProviderMocks()); const mocks = await vi.hoisted(async () => (await import('../providerTests')).makeProviderMocks());
vi.mock('@/providers/all', () => mocks); vi.mock('@/providers/all', () => mocks);
const features: FeatureMap = { const features: FeatureMap = {

View File

@ -1,6 +1,6 @@
import { mockEmbeds, mockSources } from '@/__test__/providerTests'; import { mockEmbeds, mockSources } from '@/__test__/providerTests';
import { makeProviders } from '@/main/builder'; import { makeProviders } from '@/main/builder';
import { targets } from '@/main/targets.ts'; import { targets } from '@/main/targets';
import { afterEach, describe, expect, it, vi } from 'vitest'; import { afterEach, describe, expect, it, vi } from 'vitest';
const mocks = await vi.hoisted(async () => (await import('../providerTests.ts')).makeProviderMocks()); const mocks = await vi.hoisted(async () => (await import('../providerTests.ts')).makeProviderMocks());

View File

@ -1,6 +1,6 @@
import { mockEmbeds, mockSources } from '@/__test__/providerTests'; import { mockEmbeds, mockSources } from '@/__test__/providerTests';
import { makeProviders } from '@/main/builder'; import { makeProviders } from '@/main/builder';
import { targets } from '@/main/targets.ts'; import { targets } from '@/main/targets';
import { afterEach, describe, expect, it, vi } from 'vitest'; import { afterEach, describe, expect, it, vi } from 'vitest';
const mocks = await vi.hoisted(async () => (await import('../providerTests.ts')).makeProviderMocks()); const mocks = await vi.hoisted(async () => (await import('../providerTests.ts')).makeProviderMocks());

View File

@ -9,7 +9,9 @@ describe('isValidStream()', () => {
it('should pass valid streams', () => { it('should pass valid streams', () => {
expect(isValidStream({ expect(isValidStream({
type: "file", type: "file",
id: "a",
flags: [], flags: [],
captions: [],
qualities: { qualities: {
"1080": { "1080": {
type: "mp4", type: "mp4",
@ -19,7 +21,9 @@ describe('isValidStream()', () => {
})).toBe(true); })).toBe(true);
expect(isValidStream({ expect(isValidStream({
type: "hls", type: "hls",
id: "a",
flags: [], flags: [],
captions: [],
playlist: "hello-world" playlist: "hello-world"
})).toBe(true); })).toBe(true);
}); });
@ -27,7 +31,9 @@ describe('isValidStream()', () => {
it('should detect empty qualities', () => { it('should detect empty qualities', () => {
expect(isValidStream({ expect(isValidStream({
type: "file", type: "file",
id: "a",
flags: [], flags: [],
captions: [],
qualities: {} qualities: {}
})).toBe(false); })).toBe(false);
}); });
@ -35,7 +41,9 @@ describe('isValidStream()', () => {
it('should detect empty stream urls', () => { it('should detect empty stream urls', () => {
expect(isValidStream({ expect(isValidStream({
type: "file", type: "file",
id: "a",
flags: [], flags: [],
captions: [],
qualities: { qualities: {
"1080": { "1080": {
type: "mp4", type: "mp4",
@ -48,7 +56,9 @@ describe('isValidStream()', () => {
it('should detect emtpy HLS playlists', () => { it('should detect emtpy HLS playlists', () => {
expect(isValidStream({ expect(isValidStream({
type: "hls", type: "hls",
id: "a",
flags: [], flags: [],
captions: [],
playlist: "", playlist: "",
})).toBe(false); })).toBe(false);
}); });

View File

@ -18,13 +18,13 @@ export type RunOutput = {
export type SourceRunOutput = { export type SourceRunOutput = {
sourceId: string; sourceId: string;
stream?: Stream; stream: Stream[];
embeds: []; embeds: [];
}; };
export type EmbedRunOutput = { export type EmbedRunOutput = {
embedId: string; embedId: string;
stream?: Stream; stream: Stream[];
}; };
export type ProviderRunnerOptions = { export type ProviderRunnerOptions = {

View File

@ -47,7 +47,7 @@ export const targetToFeatures: Record<Targets, FeatureMap> = {
requires: [], requires: [],
disallowed: [], disallowed: [],
}, },
} as const; };
export function getTargetFeatures(target: Targets): FeatureMap { export function getTargetFeatures(target: Targets): FeatureMap {
return targetToFeatures[target]; return targetToFeatures[target];

View File

@ -9,7 +9,7 @@ export type SourcererEmbed = {
export type SourcererOutput = { export type SourcererOutput = {
embeds: SourcererEmbed[]; embeds: SourcererEmbed[];
stream?: Stream; stream?: Stream[];
}; };
export type Sourcerer = { export type Sourcerer = {
@ -27,7 +27,7 @@ export function makeSourcerer(state: Sourcerer): Sourcerer {
} }
export type EmbedOutput = { export type EmbedOutput = {
stream: Stream; stream: Stream[];
}; };
export type Embed = { export type Embed = {

View File

@ -8,6 +8,7 @@ export type CaptionType = keyof typeof captionTypes;
export type Caption = { export type Caption = {
type: CaptionType; type: CaptionType;
id: string; // only unique per stream
url: string; url: string;
hasCorsRestrictions: boolean; hasCorsRestrictions: boolean;
language: string; language: string;

View File

@ -36,12 +36,15 @@ export const febboxHlsScraper = makeEmbed({
ctx.progress(70); ctx.progress(70);
return { return {
stream: { stream: [
type: 'hls', {
flags: [flags.CORS_ALLOWED], id: 'primary',
captions: await getSubtitles(ctx, id, firstStream.fid, type as MediaTypes, season, episode), type: 'hls',
playlist: `https://www.febbox.com/hls/main/${firstStream.oss_fid}.m3u8`, flags: [flags.CORS_ALLOWED],
}, captions: await getSubtitles(ctx, id, firstStream.fid, type as MediaTypes, season, episode),
playlist: `https://www.febbox.com/hls/main/${firstStream.oss_fid}.m3u8`,
},
],
}; };
}, },
}); });

View File

@ -39,12 +39,15 @@ export const febboxMp4Scraper = makeEmbed({
ctx.progress(70); ctx.progress(70);
return { return {
stream: { stream: [
captions: await getSubtitles(ctx, id, fid, type, episode, season), {
qualities, id: 'primary',
type: 'file', captions: await getSubtitles(ctx, id, fid, type, episode, season),
flags: [flags.CORS_ALLOWED], qualities,
}, type: 'file',
flags: [flags.CORS_ALLOWED],
},
],
}; };
}, },
}); });

View File

@ -54,6 +54,7 @@ export async function getSubtitles(
if (!validCode) return; if (!validCode) return;
output.push({ output.push({
id: subtitleFilePath,
language: subtitle.lang, language: subtitle.lang,
hasCorsRestrictions: true, hasCorsRestrictions: true,
type: subtitleType, type: subtitleType,

View File

@ -33,21 +33,24 @@ export const mixdropScraper = makeEmbed({
const url = link[1]; const url = link[1];
return { return {
stream: { stream: [
type: 'file', {
flags: [], id: 'primary',
captions: [], type: 'file',
qualities: { flags: [],
unknown: { captions: [],
type: 'mp4', qualities: {
url: url.startsWith('http') ? url : `https:${url}`, // URLs don't always start with the protocol unknown: {
headers: { type: 'mp4',
// MixDrop requires this header on all streams url: url.startsWith('http') ? url : `https:${url}`, // URLs don't always start with the protocol
Referer: 'https://mixdrop.co/', headers: {
// MixDrop requires this header on all streams
Referer: 'https://mixdrop.co/',
},
}, },
}, },
}, },
}, ],
}; };
}, },
}); });

View File

@ -15,17 +15,20 @@ export const mp4uploadScraper = makeEmbed({
if (!streamUrl) throw new Error('Stream url not found in embed code'); if (!streamUrl) throw new Error('Stream url not found in embed code');
return { return {
stream: { stream: [
type: 'file', {
flags: [flags.CORS_ALLOWED], id: 'primary',
captions: [], type: 'file',
qualities: { flags: [flags.CORS_ALLOWED],
'1080': { captions: [],
type: 'mp4', qualities: {
url: streamUrl, '1080': {
type: 'mp4',
url: streamUrl,
},
}, },
}, },
}, ],
}; };
}, },
}); });

View File

@ -57,12 +57,15 @@ export const smashyStreamDScraper = makeEmbed({
); );
return { return {
stream: { stream: [
playlist: playlistRes, {
type: 'hls', id: 'primary',
flags: [flags.CORS_ALLOWED], playlist: playlistRes,
captions: [], type: 'hls',
}, flags: [flags.CORS_ALLOWED],
captions: [],
},
],
}; };
}, },
}); });

View File

@ -30,6 +30,7 @@ export const smashyStreamFScraper = makeEmbed({
const captionType = getCaptionTypeFromUrl(url); const captionType = getCaptionTypeFromUrl(url);
if (!languageCode || !captionType) return null; if (!languageCode || !captionType) return null;
return { return {
id: url,
url: url.replace(',', ''), url: url.replace(',', ''),
language: languageCode, language: languageCode,
type: captionType, type: captionType,
@ -42,12 +43,15 @@ export const smashyStreamFScraper = makeEmbed({
.filter((x): x is Caption => x !== null) ?? []; .filter((x): x is Caption => x !== null) ?? [];
return { return {
stream: { stream: [
playlist: res.sourceUrls[0], {
type: 'hls', id: 'primary',
flags: [flags.CORS_ALLOWED], playlist: res.sourceUrls[0],
captions, type: 'hls',
}, flags: [flags.CORS_ALLOWED],
captions,
},
],
}; };
}, },
}); });

View File

@ -155,12 +155,15 @@ export const streamsbScraper = makeEmbed({
}, {} as Record<string, StreamFile>); }, {} as Record<string, StreamFile>);
return { return {
stream: { stream: [
type: 'file', {
flags: [flags.CORS_ALLOWED], id: 'primary',
qualities, type: 'file',
captions: [], flags: [flags.CORS_ALLOWED],
}, qualities,
captions: [],
},
],
}; };
}, },
}); });

View File

@ -110,6 +110,7 @@ export const upcloudScraper = makeEmbed({
const language = labelToLanguageCode(track.label); const language = labelToLanguageCode(track.label);
if (!language) return; if (!language) return;
captions.push({ captions.push({
id: track.file,
language, language,
hasCorsRestrictions: false, hasCorsRestrictions: false,
type, type,
@ -118,12 +119,15 @@ export const upcloudScraper = makeEmbed({
}); });
return { return {
stream: { stream: [
type: 'hls', {
playlist: sources.file, id: 'primary',
flags: [flags.CORS_ALLOWED], type: 'hls',
captions, playlist: sources.file,
}, flags: [flags.CORS_ALLOWED],
captions,
},
],
}; };
}, },
}); });

View File

@ -21,12 +21,15 @@ export const upstreamScraper = makeEmbed({
if (link) { if (link) {
return { return {
stream: { stream: [
type: 'hls', {
playlist: link[1], id: 'primary',
flags: [flags.CORS_ALLOWED], type: 'hls',
captions: [], playlist: link[1],
}, flags: [flags.CORS_ALLOWED],
captions: [],
},
],
}; };
} }
} }

View File

@ -17,12 +17,15 @@ async function universalScraper(ctx: MovieScrapeContext | ShowScrapeContext): Pr
return { return {
embeds: [], embeds: [],
stream: { stream: [
playlist: videoUrl, {
type: 'hls', id: 'primary',
flags: [flags.IP_LOCKED], playlist: videoUrl,
captions: [], type: 'hls',
}, flags: [flags.IP_LOCKED],
captions: [],
},
],
}; };
} }

View File

@ -22,12 +22,15 @@ export const remotestreamScraper = makeSourcerer({
return { return {
embeds: [], embeds: [],
stream: { stream: [
captions: [], {
playlist: playlistLink, id: 'primary',
type: 'hls', captions: [],
flags: [flags.CORS_ALLOWED], playlist: playlistLink,
}, type: 'hls',
flags: [flags.CORS_ALLOWED],
},
],
}; };
}, },
async scrapeMovie(ctx) { async scrapeMovie(ctx) {
@ -40,12 +43,15 @@ export const remotestreamScraper = makeSourcerer({
return { return {
embeds: [], embeds: [],
stream: { stream: [
captions: [], {
playlist: playlistLink, id: 'primary',
type: 'hls', captions: [],
flags: [flags.CORS_ALLOWED], playlist: playlistLink,
}, type: 'hls',
flags: [flags.CORS_ALLOWED],
},
],
}; };
}, },
}); });

View File

@ -11,6 +11,7 @@ export type Qualities = 'unknown' | '360' | '480' | '720' | '1080' | '4k';
export type FileBasedStream = { export type FileBasedStream = {
type: 'file'; type: 'file';
id: string; // only unique per output
flags: Flags[]; flags: Flags[];
qualities: Partial<Record<Qualities, StreamFile>>; qualities: Partial<Record<Qualities, StreamFile>>;
captions: Caption[]; captions: Caption[];
@ -18,6 +19,7 @@ export type FileBasedStream = {
export type HlsBasedStream = { export type HlsBasedStream = {
type: 'hls'; type: 'hls';
id: string; // only unique per output
flags: Flags[]; flags: Flags[];
playlist: string; playlist: string;
captions: Caption[]; captions: Caption[];