mirror of
https://github.com/sussy-code/providers.git
synced 2026-04-21 00:22:07 +00:00
Merge branch 'dev' of https://github.com/sussy-code/providers into dev
This commit is contained in:
commit
9b6eb8417c
4 changed files with 106 additions and 1 deletions
|
|
@ -9,6 +9,7 @@ export type CaptionType = keyof typeof captionTypes;
|
||||||
export type Caption = {
|
export type Caption = {
|
||||||
type: CaptionType;
|
type: CaptionType;
|
||||||
id: string; // only unique per stream
|
id: string; // only unique per stream
|
||||||
|
opensubtitles?: boolean;
|
||||||
url: string;
|
url: string;
|
||||||
hasCorsRestrictions: boolean;
|
hasCorsRestrictions: boolean;
|
||||||
language: string;
|
language: string;
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import { EmbedOutput, SourcererOutput } from '@/providers/base';
|
||||||
import { ProviderList } from '@/providers/get';
|
import { ProviderList } from '@/providers/get';
|
||||||
import { ScrapeContext } from '@/utils/context';
|
import { ScrapeContext } from '@/utils/context';
|
||||||
import { NotFoundError } from '@/utils/errors';
|
import { NotFoundError } from '@/utils/errors';
|
||||||
|
import { addOpenSubtitlesCaptions } from '@/utils/opensubtitles';
|
||||||
import { isValidStream, validatePlayableStreams } from '@/utils/valid';
|
import { isValidStream, validatePlayableStreams } from '@/utils/valid';
|
||||||
|
|
||||||
export type IndividualSourceRunnerOptions = {
|
export type IndividualSourceRunnerOptions = {
|
||||||
|
|
@ -66,6 +67,14 @@ export async function scrapeInvidualSource(
|
||||||
return true;
|
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)
|
if ((!output.stream || output.stream.length === 0) && output.embeds.length === 0)
|
||||||
throw new NotFoundError('No streams found');
|
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) {
|
if (output.stream && output.stream.length > 0 && output.embeds.length === 0) {
|
||||||
const playableStreams = await validatePlayableStreams(output.stream, ops, sourceScraper.id);
|
const playableStreams = await validatePlayableStreams(output.stream, ops, sourceScraper.id);
|
||||||
if (playableStreams.length === 0) throw new NotFoundError('No playable streams found');
|
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;
|
output.stream = playableStreams;
|
||||||
}
|
}
|
||||||
return output;
|
return output;
|
||||||
|
|
@ -94,10 +116,14 @@ export async function scrapeIndividualEmbed(
|
||||||
const embedScraper = list.embeds.find((v) => ops.id === v.id);
|
const embedScraper = list.embeds.find((v) => ops.id === v.id);
|
||||||
if (!embedScraper) throw new Error('Embed with ID not found');
|
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({
|
const output = await embedScraper.scrape({
|
||||||
fetcher: ops.fetcher,
|
fetcher: ops.fetcher,
|
||||||
proxiedFetcher: ops.proxiedFetcher,
|
proxiedFetcher: ops.proxiedFetcher,
|
||||||
url: ops.url,
|
url,
|
||||||
progress(val) {
|
progress(val) {
|
||||||
ops.events?.update?.({
|
ops.events?.update?.({
|
||||||
id: embedScraper.id,
|
id: embedScraper.id,
|
||||||
|
|
@ -114,6 +140,11 @@ export async function scrapeIndividualEmbed(
|
||||||
|
|
||||||
const playableStreams = await validatePlayableStreams(output.stream, ops, embedScraper.id);
|
const playableStreams = await validatePlayableStreams(output.stream, ops, embedScraper.id);
|
||||||
if (playableStreams.length === 0) throw new NotFoundError('No playable streams found');
|
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;
|
output.stream = playableStreams;
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import { Stream } from '@/providers/streams';
|
||||||
import { ScrapeContext } from '@/utils/context';
|
import { ScrapeContext } from '@/utils/context';
|
||||||
import { NotFoundError } from '@/utils/errors';
|
import { NotFoundError } from '@/utils/errors';
|
||||||
import { reorderOnIdList } from '@/utils/list';
|
import { reorderOnIdList } from '@/utils/list';
|
||||||
|
import { addOpenSubtitlesCaptions } from '@/utils/opensubtitles';
|
||||||
import { isValidStream, validatePlayableStream } from '@/utils/valid';
|
import { isValidStream, validatePlayableStream } from '@/utils/valid';
|
||||||
|
|
||||||
export type RunOutput = {
|
export type RunOutput = {
|
||||||
|
|
@ -106,6 +107,18 @@ export async function runAllProviders(list: ProviderList, ops: ProviderRunnerOpt
|
||||||
if (output.stream?.[0]) {
|
if (output.stream?.[0]) {
|
||||||
const playableStream = await validatePlayableStream(output.stream[0], ops, source.id);
|
const playableStream = await validatePlayableStream(output.stream[0], ops, source.id);
|
||||||
if (!playableStream) throw new NotFoundError('No streams found');
|
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 {
|
return {
|
||||||
sourceId: source.id,
|
sourceId: source.id,
|
||||||
stream: playableStream,
|
stream: playableStream,
|
||||||
|
|
@ -153,6 +166,17 @@ export async function runAllProviders(list: ProviderList, ops: ProviderRunnerOpt
|
||||||
}
|
}
|
||||||
const playableStream = await validatePlayableStream(embedOutput.stream[0], ops, embed.embedId);
|
const playableStream = await validatePlayableStream(embedOutput.stream[0], ops, embed.embedId);
|
||||||
if (!playableStream) throw new NotFoundError('No streams found');
|
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];
|
embedOutput.stream = [playableStream];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const updateParams: UpdateEvent = {
|
const updateParams: UpdateEvent = {
|
||||||
|
|
|
||||||
49
src/utils/opensubtitles.ts
Normal file
49
src/utils/opensubtitles.ts
Normal file
|
|
@ -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<Caption[]> {
|
||||||
|
try {
|
||||||
|
const [imdbId, season, episode] = atob(media)
|
||||||
|
.split('.')
|
||||||
|
.map((x, i) => (i === 0 ? x : Number(x) || null));
|
||||||
|
if (!imdbId) return captions;
|
||||||
|
const Res: {
|
||||||
|
LanguageName: string;
|
||||||
|
SubDownloadLink: string;
|
||||||
|
SubFormat: 'srt' | 'vtt';
|
||||||
|
}[] = await ops.proxiedFetcher(
|
||||||
|
`https://rest.opensubtitles.org/search/${
|
||||||
|
season && episode ? `episode-${episode}/` : ''
|
||||||
|
}imdbid-${(imdbId as string).slice(2)}${season && episode ? `/season-${season}` : ''}`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'X-User-Agent': 'VLSub 0.10.2',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const openSubtilesCaptions: Caption[] = [];
|
||||||
|
for (const caption of Res) {
|
||||||
|
const url = caption.SubDownloadLink.replace('.gz', '').replace('download/', 'download/subencoding-utf8/');
|
||||||
|
const language = labelToLanguageCode(caption.LanguageName);
|
||||||
|
if (!url || !language) continue;
|
||||||
|
else
|
||||||
|
openSubtilesCaptions.push({
|
||||||
|
id: url,
|
||||||
|
opensubtitles: true,
|
||||||
|
url,
|
||||||
|
type: caption.SubFormat || 'srt',
|
||||||
|
hasCorsRestrictions: false,
|
||||||
|
language,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return [...captions, ...removeDuplicatedLanguages(openSubtilesCaptions)];
|
||||||
|
} catch {
|
||||||
|
return captions;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue