diff --git a/src/providers/captions.ts b/src/providers/captions.ts index 92e5db3..d64dcc2 100644 --- a/src/providers/captions.ts +++ b/src/providers/captions.ts @@ -9,6 +9,7 @@ export type CaptionType = keyof typeof captionTypes; export type Caption = { type: CaptionType; id: string; // only unique per stream + opensubtitles?: boolean; url: string; hasCorsRestrictions: boolean; language: string; diff --git a/src/runners/individualRunner.ts b/src/runners/individualRunner.ts index b309180..da7fca9 100644 --- a/src/runners/individualRunner.ts +++ b/src/runners/individualRunner.ts @@ -6,6 +6,7 @@ import { EmbedOutput, SourcererOutput } from '@/providers/base'; import { ProviderList } from '@/providers/get'; import { ScrapeContext } from '@/utils/context'; import { NotFoundError } from '@/utils/errors'; +import { addOpenSubtitlesCaptions } from '@/utils/opensubtitles'; import { isValidStream, validatePlayableStreams } from '@/utils/valid'; export type IndividualSourceRunnerOptions = { @@ -66,6 +67,14 @@ export async function scrapeInvidualSource( return true; }); + // opensubtitles + for (const embed of output.embeds) + embed.url = `${embed.url}${btoa('MEDIA=')}${btoa( + `${ops.media.imdbId}${ + ops.media.type === 'show' ? `.${ops.media.season.number}.${ops.media.episode.number}` : '' + }`, + )}`; + if ((!output.stream || output.stream.length === 0) && output.embeds.length === 0) throw new NotFoundError('No streams found'); @@ -73,6 +82,19 @@ export async function scrapeInvidualSource( if (output.stream && output.stream.length > 0 && output.embeds.length === 0) { const playableStreams = await validatePlayableStreams(output.stream, ops, sourceScraper.id); if (playableStreams.length === 0) throw new NotFoundError('No playable streams found'); + + // opensubtitles + for (const playableStream of playableStreams) { + playableStream.captions = await addOpenSubtitlesCaptions( + playableStream.captions, + ops, + btoa( + `${ops.media.imdbId}${ + ops.media.type === 'show' ? `.${ops.media.season.number}.${ops.media.episode.number}` : '' + }`, + ), + ); + } output.stream = playableStreams; } return output; @@ -94,10 +116,14 @@ export async function scrapeIndividualEmbed( const embedScraper = list.embeds.find((v) => ops.id === v.id); if (!embedScraper) throw new Error('Embed with ID not found'); + let url = ops.url; + let media; + if (ops.url.includes(btoa('MEDIA='))) [url, media] = url.split(btoa('MEDIA=')); + const output = await embedScraper.scrape({ fetcher: ops.fetcher, proxiedFetcher: ops.proxiedFetcher, - url: ops.url, + url, progress(val) { ops.events?.update?.({ id: embedScraper.id, @@ -114,6 +140,11 @@ export async function scrapeIndividualEmbed( const playableStreams = await validatePlayableStreams(output.stream, ops, embedScraper.id); if (playableStreams.length === 0) throw new NotFoundError('No playable streams found'); + + if (media) + for (const playableStream of playableStreams) + playableStream.captions = await addOpenSubtitlesCaptions(playableStream.captions, ops, media); + output.stream = playableStreams; return output; diff --git a/src/runners/runner.ts b/src/runners/runner.ts index c5f5de3..e6dea21 100644 --- a/src/runners/runner.ts +++ b/src/runners/runner.ts @@ -8,6 +8,7 @@ import { Stream } from '@/providers/streams'; import { ScrapeContext } from '@/utils/context'; import { NotFoundError } from '@/utils/errors'; import { reorderOnIdList } from '@/utils/list'; +import { addOpenSubtitlesCaptions } from '@/utils/opensubtitles'; import { isValidStream, validatePlayableStream } from '@/utils/valid'; export type RunOutput = { @@ -106,6 +107,18 @@ export async function runAllProviders(list: ProviderList, ops: ProviderRunnerOpt if (output.stream?.[0]) { const playableStream = await validatePlayableStream(output.stream[0], ops, source.id); if (!playableStream) throw new NotFoundError('No streams found'); + + // opensubtitles + playableStream.captions = await addOpenSubtitlesCaptions( + playableStream.captions, + ops, + btoa( + `${ops.media.imdbId}${ + ops.media.type === 'show' ? `.${ops.media.season.number}.${ops.media.episode.number}` : '' + }`, + ), + ); + return { sourceId: source.id, stream: playableStream, @@ -153,6 +166,17 @@ export async function runAllProviders(list: ProviderList, ops: ProviderRunnerOpt } const playableStream = await validatePlayableStream(embedOutput.stream[0], ops, embed.embedId); if (!playableStream) throw new NotFoundError('No streams found'); + + // opensubtitles + playableStream.captions = await addOpenSubtitlesCaptions( + playableStream.captions, + ops, + btoa( + `${ops.media.imdbId}${ + ops.media.type === 'show' ? `.${ops.media.season.number}.${ops.media.episode.number}` : '' + }`, + ), + ); embedOutput.stream = [playableStream]; } catch (error) { const updateParams: UpdateEvent = { diff --git a/src/utils/opensubtitles.ts b/src/utils/opensubtitles.ts new file mode 100644 index 0000000..559536c --- /dev/null +++ b/src/utils/opensubtitles.ts @@ -0,0 +1,49 @@ +import { Caption, labelToLanguageCode, removeDuplicatedLanguages } from '@/providers/captions'; +import { IndividualEmbedRunnerOptions } from '@/runners/individualRunner'; +import { ProviderRunnerOptions } from '@/runners/runner'; + +export async function addOpenSubtitlesCaptions( + captions: Caption[], + ops: ProviderRunnerOptions | IndividualEmbedRunnerOptions, + media: string, +): Promise