From 0affe83d24ce4a7ba5a2182ef2f7b8f17f35bd93 Mon Sep 17 00:00:00 2001 From: mrjvs Date: Sun, 24 Dec 2023 19:46:12 +0100 Subject: [PATCH] Allow embeds and videos to return multiple streams + add identifiers to list returns --- src/__test__/providers/checks.test.ts | 4 +-- src/__test__/runner/list.test.ts | 2 +- src/__test__/runner/meta.test.ts | 2 +- src/__test__/utils/valid.test.ts | 10 +++++++ src/main/runner.ts | 4 +-- src/main/targets.ts | 2 +- src/providers/base.ts | 4 +-- src/providers/captions.ts | 1 + src/providers/embeds/febbox/hls.ts | 15 ++++++----- src/providers/embeds/febbox/mp4.ts | 15 ++++++----- src/providers/embeds/febbox/subtitles.ts | 1 + src/providers/embeds/mixdrop.ts | 27 ++++++++++--------- src/providers/embeds/mp4upload.ts | 21 ++++++++------- src/providers/embeds/smashystream/dued.ts | 15 ++++++----- src/providers/embeds/smashystream/video1.ts | 16 ++++++----- src/providers/embeds/streamsb.ts | 15 ++++++----- src/providers/embeds/upcloud.ts | 16 ++++++----- src/providers/embeds/upstream.ts | 15 ++++++----- src/providers/sources/lookmovie/index.ts | 15 ++++++----- src/providers/sources/remotestream.ts | 30 ++++++++++++--------- src/providers/streams.ts | 2 ++ 21 files changed, 142 insertions(+), 90 deletions(-) diff --git a/src/__test__/providers/checks.test.ts b/src/__test__/providers/checks.test.ts index 7ffa845..1396c9d 100644 --- a/src/__test__/providers/checks.test.ts +++ b/src/__test__/providers/checks.test.ts @@ -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 = { diff --git a/src/__test__/runner/list.test.ts b/src/__test__/runner/list.test.ts index 483485e..922392f 100644 --- a/src/__test__/runner/list.test.ts +++ b/src/__test__/runner/list.test.ts @@ -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()); diff --git a/src/__test__/runner/meta.test.ts b/src/__test__/runner/meta.test.ts index 4b0e55e..a5cdbf4 100644 --- a/src/__test__/runner/meta.test.ts +++ b/src/__test__/runner/meta.test.ts @@ -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()); diff --git a/src/__test__/utils/valid.test.ts b/src/__test__/utils/valid.test.ts index 540fbf3..eab835d 100644 --- a/src/__test__/utils/valid.test.ts +++ b/src/__test__/utils/valid.test.ts @@ -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); }); diff --git a/src/main/runner.ts b/src/main/runner.ts index 700dbe4..cea7106 100644 --- a/src/main/runner.ts +++ b/src/main/runner.ts @@ -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 = { diff --git a/src/main/targets.ts b/src/main/targets.ts index 66d6040..1527272 100644 --- a/src/main/targets.ts +++ b/src/main/targets.ts @@ -47,7 +47,7 @@ export const targetToFeatures: Record = { requires: [], disallowed: [], }, -} as const; +}; export function getTargetFeatures(target: Targets): FeatureMap { return targetToFeatures[target]; diff --git a/src/providers/base.ts b/src/providers/base.ts index 022cd87..d503ecd 100644 --- a/src/providers/base.ts +++ b/src/providers/base.ts @@ -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 = { diff --git a/src/providers/captions.ts b/src/providers/captions.ts index 791bfa6..ce3f398 100644 --- a/src/providers/captions.ts +++ b/src/providers/captions.ts @@ -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; diff --git a/src/providers/embeds/febbox/hls.ts b/src/providers/embeds/febbox/hls.ts index e0d48e0..78cd4ea 100644 --- a/src/providers/embeds/febbox/hls.ts +++ b/src/providers/embeds/febbox/hls.ts @@ -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`, + }, + ], }; }, }); diff --git a/src/providers/embeds/febbox/mp4.ts b/src/providers/embeds/febbox/mp4.ts index 520a35e..bcebbff 100644 --- a/src/providers/embeds/febbox/mp4.ts +++ b/src/providers/embeds/febbox/mp4.ts @@ -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], + }, + ], }; }, }); diff --git a/src/providers/embeds/febbox/subtitles.ts b/src/providers/embeds/febbox/subtitles.ts index a0394ec..b1b3064 100644 --- a/src/providers/embeds/febbox/subtitles.ts +++ b/src/providers/embeds/febbox/subtitles.ts @@ -54,6 +54,7 @@ export async function getSubtitles( if (!validCode) return; output.push({ + id: subtitleFilePath, language: subtitle.lang, hasCorsRestrictions: true, type: subtitleType, diff --git a/src/providers/embeds/mixdrop.ts b/src/providers/embeds/mixdrop.ts index 007738a..71fd0ac 100644 --- a/src/providers/embeds/mixdrop.ts +++ b/src/providers/embeds/mixdrop.ts @@ -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/', + }, }, }, }, - }, + ], }; }, }); diff --git a/src/providers/embeds/mp4upload.ts b/src/providers/embeds/mp4upload.ts index 72aef8a..69547d8 100644 --- a/src/providers/embeds/mp4upload.ts +++ b/src/providers/embeds/mp4upload.ts @@ -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, + }, }, }, - }, + ], }; }, }); diff --git a/src/providers/embeds/smashystream/dued.ts b/src/providers/embeds/smashystream/dued.ts index df45af2..a143cbc 100644 --- a/src/providers/embeds/smashystream/dued.ts +++ b/src/providers/embeds/smashystream/dued.ts @@ -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: [], + }, + ], }; }, }); diff --git a/src/providers/embeds/smashystream/video1.ts b/src/providers/embeds/smashystream/video1.ts index 61172fc..391e67c 100644 --- a/src/providers/embeds/smashystream/video1.ts +++ b/src/providers/embeds/smashystream/video1.ts @@ -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, + }, + ], }; }, }); diff --git a/src/providers/embeds/streamsb.ts b/src/providers/embeds/streamsb.ts index 3b0cb7d..98954e5 100644 --- a/src/providers/embeds/streamsb.ts +++ b/src/providers/embeds/streamsb.ts @@ -155,12 +155,15 @@ export const streamsbScraper = makeEmbed({ }, {} as Record); return { - stream: { - type: 'file', - flags: [flags.CORS_ALLOWED], - qualities, - captions: [], - }, + stream: [ + { + id: 'primary', + type: 'file', + flags: [flags.CORS_ALLOWED], + qualities, + captions: [], + }, + ], }; }, }); diff --git a/src/providers/embeds/upcloud.ts b/src/providers/embeds/upcloud.ts index 514d4f1..44e39af 100644 --- a/src/providers/embeds/upcloud.ts +++ b/src/providers/embeds/upcloud.ts @@ -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, + }, + ], }; }, }); diff --git a/src/providers/embeds/upstream.ts b/src/providers/embeds/upstream.ts index 2b62ee9..ba25056 100644 --- a/src/providers/embeds/upstream.ts +++ b/src/providers/embeds/upstream.ts @@ -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: [], + }, + ], }; } } diff --git a/src/providers/sources/lookmovie/index.ts b/src/providers/sources/lookmovie/index.ts index 08e8e64..606a6e3 100644 --- a/src/providers/sources/lookmovie/index.ts +++ b/src/providers/sources/lookmovie/index.ts @@ -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: [], + }, + ], }; } diff --git a/src/providers/sources/remotestream.ts b/src/providers/sources/remotestream.ts index f3ea478..f97cf4d 100644 --- a/src/providers/sources/remotestream.ts +++ b/src/providers/sources/remotestream.ts @@ -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], + }, + ], }; }, }); diff --git a/src/providers/streams.ts b/src/providers/streams.ts index 1ba4c9a..9a9a9be 100644 --- a/src/providers/streams.ts +++ b/src/providers/streams.ts @@ -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>; 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[];