mirror of
https://github.com/p-stream/providers.git
synced 2026-04-21 18:22:18 +00:00
remove external subtitle scraping
This commit is contained in:
parent
6bea2a2ee5
commit
970fb7e598
6 changed files with 2 additions and 275 deletions
|
|
@ -94,8 +94,7 @@
|
||||||
"nanoid": "^3.3.8",
|
"nanoid": "^3.3.8",
|
||||||
"node-fetch": "^3.3.2",
|
"node-fetch": "^3.3.2",
|
||||||
"set-cookie-parser": "^2.7.1",
|
"set-cookie-parser": "^2.7.1",
|
||||||
"unpacker": "^1.0.1",
|
"unpacker": "^1.0.1"
|
||||||
"wyzie-lib": "^2.2.5"
|
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@9.14.4"
|
"packageManager": "pnpm@9.14.4"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,9 +44,6 @@ importers:
|
||||||
unpacker:
|
unpacker:
|
||||||
specifier: ^1.0.1
|
specifier: ^1.0.1
|
||||||
version: 1.0.1
|
version: 1.0.1
|
||||||
wyzie-lib:
|
|
||||||
specifier: ^2.2.5
|
|
||||||
version: 2.2.5
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@nabla/vite-plugin-eslint':
|
'@nabla/vite-plugin-eslint':
|
||||||
specifier: ^2.0.5
|
specifier: ^2.0.5
|
||||||
|
|
@ -2539,9 +2536,6 @@ packages:
|
||||||
utf-8-validate:
|
utf-8-validate:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
wyzie-lib@2.2.5:
|
|
||||||
resolution: {integrity: sha512-hOvAqS0VJhqTi2TRFkQ1YSEvX5ltrKC+ZWGiGDZ2Qn6b2t5Ov5DJILFCO2W/1ScK+BKOKHen7tRidatj1W62oA==}
|
|
||||||
|
|
||||||
y18n@5.0.8:
|
y18n@5.0.8:
|
||||||
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
|
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
@ -5248,8 +5242,6 @@ snapshots:
|
||||||
|
|
||||||
ws@8.18.0: {}
|
ws@8.18.0: {}
|
||||||
|
|
||||||
wyzie-lib@2.2.5: {}
|
|
||||||
|
|
||||||
y18n@5.0.8: {}
|
y18n@5.0.8: {}
|
||||||
|
|
||||||
yallist@4.0.0: {}
|
yallist@4.0.0: {}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ 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 { requiresProxy, setupProxy } from '@/utils/proxy';
|
import { requiresProxy, setupProxy } from '@/utils/proxy';
|
||||||
import { isValidStream, validatePlayableStreams } from '@/utils/valid';
|
import { isValidStream, validatePlayableStreams } from '@/utils/valid';
|
||||||
|
|
||||||
|
|
@ -18,7 +17,6 @@ export type IndividualSourceRunnerOptions = {
|
||||||
id: string;
|
id: string;
|
||||||
events?: IndividualScraperEvents;
|
events?: IndividualScraperEvents;
|
||||||
proxyStreams?: boolean; // temporary
|
proxyStreams?: boolean; // temporary
|
||||||
disableOpensubtitles?: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function scrapeInvidualSource(
|
export async function scrapeInvidualSource(
|
||||||
|
|
@ -74,15 +72,6 @@ export async function scrapeInvidualSource(
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
// opensubtitles
|
|
||||||
if (!ops.disableOpensubtitles)
|
|
||||||
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');
|
||||||
|
|
||||||
|
|
@ -91,20 +80,6 @@ export async function scrapeInvidualSource(
|
||||||
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
|
|
||||||
if (!ops.disableOpensubtitles) {
|
|
||||||
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;
|
||||||
|
|
@ -118,7 +93,6 @@ export type IndividualEmbedRunnerOptions = {
|
||||||
id: string;
|
id: string;
|
||||||
events?: IndividualScraperEvents;
|
events?: IndividualScraperEvents;
|
||||||
proxyStreams?: boolean; // temporary
|
proxyStreams?: boolean; // temporary
|
||||||
disableOpensubtitles?: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function scrapeIndividualEmbed(
|
export async function scrapeIndividualEmbed(
|
||||||
|
|
@ -128,9 +102,7 @@ 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;
|
const 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,
|
||||||
|
|
@ -157,22 +129,6 @@ 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 && !ops.disableOpensubtitles) {
|
|
||||||
const [imdbId, season, episode] = atob(media)
|
|
||||||
.split('.')
|
|
||||||
.map((x, i) => (i === 0 ? x : Number(x) || null));
|
|
||||||
const mediaInfo = {
|
|
||||||
...ops,
|
|
||||||
media: {
|
|
||||||
type: season && episode ? 'show' : 'movie',
|
|
||||||
imdbId: imdbId?.toString() || '',
|
|
||||||
...(season && episode ? { season: { number: season }, episode: { number: episode } } : {}),
|
|
||||||
} as ScrapeMedia,
|
|
||||||
};
|
|
||||||
for (const playableStream of playableStreams)
|
|
||||||
playableStream.captions = await addOpenSubtitlesCaptions(playableStream.captions, mediaInfo, media);
|
|
||||||
}
|
|
||||||
|
|
||||||
output.stream = playableStreams;
|
output.stream = playableStreams;
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ 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 { requiresProxy, setupProxy } from '@/utils/proxy';
|
import { requiresProxy, setupProxy } from '@/utils/proxy';
|
||||||
import { isValidStream, validatePlayableStream } from '@/utils/valid';
|
import { isValidStream, validatePlayableStream } from '@/utils/valid';
|
||||||
|
|
||||||
|
|
@ -38,7 +37,6 @@ export type ProviderRunnerOptions = {
|
||||||
events?: FullScraperEvents;
|
events?: FullScraperEvents;
|
||||||
media: ScrapeMedia;
|
media: ScrapeMedia;
|
||||||
proxyStreams?: boolean; // temporary
|
proxyStreams?: boolean; // temporary
|
||||||
disableOpensubtitles?: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function runAllProviders(list: ProviderList, ops: ProviderRunnerOptions): Promise<RunOutput | null> {
|
export async function runAllProviders(list: ProviderList, ops: ProviderRunnerOptions): Promise<RunOutput | null> {
|
||||||
|
|
@ -115,21 +113,6 @@ export async function runAllProviders(list: ProviderList, ops: ProviderRunnerOpt
|
||||||
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
|
|
||||||
if (!ops.disableOpensubtitles) {
|
|
||||||
if (ops.media.imdbId) {
|
|
||||||
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,
|
||||||
|
|
@ -181,20 +164,6 @@ 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
|
|
||||||
if (!ops.disableOpensubtitles) {
|
|
||||||
if (ops.media.imdbId) {
|
|
||||||
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 = {
|
||||||
|
|
|
||||||
|
|
@ -1,110 +0,0 @@
|
||||||
/* eslint-disable no-console */
|
|
||||||
import { ScrapeMedia } from '@/entrypoint/utils/media';
|
|
||||||
import { Caption, labelToLanguageCode } from '@/providers/captions';
|
|
||||||
import { IndividualEmbedRunnerOptions } from '@/runners/individualRunner';
|
|
||||||
import { ProviderRunnerOptions } from '@/runners/runner';
|
|
||||||
|
|
||||||
import { addWyzieCaptions } from './wyziesubs';
|
|
||||||
|
|
||||||
type CaptionOptions = (ProviderRunnerOptions | IndividualEmbedRunnerOptions) & {
|
|
||||||
media?: ScrapeMedia;
|
|
||||||
};
|
|
||||||
|
|
||||||
const timeout = (ms: number, source: string) =>
|
|
||||||
new Promise<null>((resolve) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
console.error(`${source} captions request timed out after ${ms}ms`);
|
|
||||||
resolve(null);
|
|
||||||
}, ms);
|
|
||||||
});
|
|
||||||
|
|
||||||
export async function addOpenSubtitlesCaptions(
|
|
||||||
captions: Caption[],
|
|
||||||
ops: CaptionOptions,
|
|
||||||
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 allCaptions = [...captions];
|
|
||||||
|
|
||||||
// Fetch Wyzie captions with 2 second timeout
|
|
||||||
const wyziePromise = addWyzieCaptions(
|
|
||||||
[],
|
|
||||||
ops.media?.tmdbId?.toString() || '',
|
|
||||||
imdbId.toString(),
|
|
||||||
typeof season === 'number' ? season : undefined,
|
|
||||||
typeof episode === 'number' ? episode : undefined,
|
|
||||||
)
|
|
||||||
.then((wyzieCaptions) => {
|
|
||||||
if (wyzieCaptions && wyzieCaptions.length > 0) {
|
|
||||||
return wyzieCaptions.map((caption) => ({
|
|
||||||
...caption,
|
|
||||||
opensubtitles: true,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error('Wyzie subtitles fetch failed:', error);
|
|
||||||
return [];
|
|
||||||
});
|
|
||||||
|
|
||||||
// Fetch OpenSubtitles captions with 5 second timeout
|
|
||||||
const openSubsPromise = 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',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.then((Res) => {
|
|
||||||
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 openSubtilesCaptions;
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error('OpenSubtitles fetch failed:', error);
|
|
||||||
return [];
|
|
||||||
});
|
|
||||||
|
|
||||||
// Wait for both promises with their respective timeouts
|
|
||||||
const [wyzieCaptions, openSubsCaptions] = await Promise.all([
|
|
||||||
Promise.race([wyziePromise, timeout(2000, 'Wyzie')]),
|
|
||||||
Promise.race([openSubsPromise, timeout(5000, 'OpenSubtitles')]),
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Debug logging
|
|
||||||
// console.log('Wyzie captions found:', wyzieCaptions?.length || 0);
|
|
||||||
// console.log('OpenSubtitles captions found:', openSubsCaptions?.length || 0);
|
|
||||||
|
|
||||||
// Add any successful captions to our result
|
|
||||||
if (wyzieCaptions) allCaptions.push(...wyzieCaptions);
|
|
||||||
if (openSubsCaptions) allCaptions.push(...openSubsCaptions);
|
|
||||||
|
|
||||||
return allCaptions;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error in addOpenSubtitlesCaptions:', error);
|
|
||||||
return captions;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,79 +0,0 @@
|
||||||
/* eslint-disable no-console */
|
|
||||||
import { type SubtitleData, searchSubtitles } from 'wyzie-lib';
|
|
||||||
|
|
||||||
import { Caption } from '@/providers/captions';
|
|
||||||
|
|
||||||
// function isSubdlUrl(url: string) {
|
|
||||||
// return url.endsWith('.subdl');
|
|
||||||
// }
|
|
||||||
|
|
||||||
// export function filterSubtitles(list: Caption[]) {
|
|
||||||
// const selected: Record<string, Caption> = {};
|
|
||||||
|
|
||||||
// for (const sub of list) {
|
|
||||||
// const existing = selected[sub.language];
|
|
||||||
|
|
||||||
// if (!existing) {
|
|
||||||
// selected[sub.language] = sub;
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const existingIsSubdl = isSubdlUrl(existing.url);
|
|
||||||
// const currentIsSubdl = isSubdlUrl(sub.url);
|
|
||||||
|
|
||||||
// if (existingIsSubdl && !currentIsSubdl) {
|
|
||||||
// selected[sub.language] = sub;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return Object.values(selected);
|
|
||||||
// }
|
|
||||||
|
|
||||||
export async function addWyzieCaptions(
|
|
||||||
captions: Caption[],
|
|
||||||
tmdbId: string | number,
|
|
||||||
imdbId: string,
|
|
||||||
season?: number,
|
|
||||||
episode?: number,
|
|
||||||
): Promise<Caption[]> {
|
|
||||||
try {
|
|
||||||
const searchParams: any = {
|
|
||||||
encoding: 'utf-8',
|
|
||||||
source: 'all',
|
|
||||||
imdb_id: imdbId,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (tmdbId && !imdbId) {
|
|
||||||
searchParams.tmdb_id = typeof tmdbId === 'string' ? parseInt(tmdbId, 10) : tmdbId;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (season && episode) {
|
|
||||||
searchParams.season = season;
|
|
||||||
searchParams.episode = episode;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Searching Wyzie subtitles with params:', searchParams);
|
|
||||||
const wyzieSubtitles: SubtitleData[] = await searchSubtitles(searchParams);
|
|
||||||
// console.log('Found Wyzie subtitles:', wyzieSubtitles);
|
|
||||||
|
|
||||||
const wyzieCaptions: Caption[] = wyzieSubtitles.map((subtitle) => ({
|
|
||||||
id: subtitle.id,
|
|
||||||
url: subtitle.url,
|
|
||||||
type: subtitle.format === 'srt' || subtitle.format === 'vtt' ? subtitle.format : 'srt',
|
|
||||||
hasCorsRestrictions: false,
|
|
||||||
language: subtitle.language,
|
|
||||||
// Additional metadata from Wyzie
|
|
||||||
flagUrl: subtitle.flagUrl,
|
|
||||||
display: subtitle.display,
|
|
||||||
media: subtitle.media,
|
|
||||||
isHearingImpaired: subtitle.isHearingImpaired,
|
|
||||||
source: typeof subtitle.source === 'number' ? subtitle.source.toString() : subtitle.source,
|
|
||||||
encoding: subtitle.encoding,
|
|
||||||
}));
|
|
||||||
|
|
||||||
return [...captions, ...wyzieCaptions];
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching Wyzie subtitles:', error);
|
|
||||||
return captions;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in a new issue