Allow embeds and videos to return multiple streams + add identifiers to list returns
This commit is contained in:
parent
d44320e362
commit
0affe83d24
|
@ -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 = {
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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];
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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`,
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -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],
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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/',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -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,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -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: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -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,
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -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: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -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,
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -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: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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],
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -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[];
|
||||||
|
|
Loading…
Reference in New Issue