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 { FeatureMap } from '@/main/targets.ts';
import { FeatureMap } from '@/main/targets';
import { getProviders } from '@/providers/get';
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);
const features: FeatureMap = {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -36,12 +36,15 @@ export const febboxHlsScraper = makeEmbed({
ctx.progress(70);
return {
stream: {
type: 'hls',
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`,
},
stream: [
{
id: 'primary',
type: 'hls',
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);
return {
stream: {
captions: await getSubtitles(ctx, id, fid, type, episode, season),
qualities,
type: 'file',
flags: [flags.CORS_ALLOWED],
},
stream: [
{
id: 'primary',
captions: await getSubtitles(ctx, id, fid, type, episode, season),
qualities,
type: 'file',
flags: [flags.CORS_ALLOWED],
},
],
};
},
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -22,12 +22,15 @@ export const remotestreamScraper = makeSourcerer({
return {
embeds: [],
stream: {
captions: [],
playlist: playlistLink,
type: 'hls',
flags: [flags.CORS_ALLOWED],
},
stream: [
{
id: 'primary',
captions: [],
playlist: playlistLink,
type: 'hls',
flags: [flags.CORS_ALLOWED],
},
],
};
},
async scrapeMovie(ctx) {
@ -40,12 +43,15 @@ export const remotestreamScraper = makeSourcerer({
return {
embeds: [],
stream: {
captions: [],
playlist: playlistLink,
type: 'hls',
flags: [flags.CORS_ALLOWED],
},
stream: [
{
id: 'primary',
captions: [],
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 = {
type: 'file';
id: string; // only unique per output
flags: Flags[];
qualities: Partial<Record<Qualities, StreamFile>>;
captions: Caption[];
@ -18,6 +19,7 @@ export type FileBasedStream = {
export type HlsBasedStream = {
type: 'hls';
id: string; // only unique per output
flags: Flags[];
playlist: string;
captions: Caption[];