mirror of
https://github.com/p-stream/p-stream.git
synced 2026-04-20 23:12:16 +00:00
Refactor externalSubtitles into modular files
Split the monolithic externalSubtitles.ts into separate modules for each provider (febbox, opensubtitles, vdrk, wyzie) and a new index.ts for orchestration and exports. This improves maintainability and clarity by isolating provider-specific logic.
This commit is contained in:
parent
b875bc93aa
commit
d2768f558c
6 changed files with 658 additions and 440 deletions
|
|
@ -1,440 +0,0 @@
|
||||||
/* eslint-disable no-console */
|
|
||||||
import { type SubtitleData, searchSubtitles } from "wyzie-lib";
|
|
||||||
|
|
||||||
import { CaptionListItem, PlayerMeta } from "@/stores/player/slices/source";
|
|
||||||
|
|
||||||
// Helper function to convert language names to language codes
|
|
||||||
function labelToLanguageCode(languageName: string): string {
|
|
||||||
const languageMap: Record<string, string> = {
|
|
||||||
English: "en",
|
|
||||||
Spanish: "es",
|
|
||||||
French: "fr",
|
|
||||||
German: "de",
|
|
||||||
Italian: "it",
|
|
||||||
Portuguese: "pt",
|
|
||||||
Russian: "ru",
|
|
||||||
Japanese: "ja",
|
|
||||||
Korean: "ko",
|
|
||||||
Chinese: "zh",
|
|
||||||
Arabic: "ar",
|
|
||||||
Hindi: "hi",
|
|
||||||
Turkish: "tr",
|
|
||||||
Dutch: "nl",
|
|
||||||
Polish: "pl",
|
|
||||||
Swedish: "sv",
|
|
||||||
Norwegian: "no",
|
|
||||||
Danish: "da",
|
|
||||||
Finnish: "fi",
|
|
||||||
Greek: "el",
|
|
||||||
Hebrew: "he",
|
|
||||||
Thai: "th",
|
|
||||||
Vietnamese: "vi",
|
|
||||||
Indonesian: "id",
|
|
||||||
Malay: "ms",
|
|
||||||
Filipino: "tl",
|
|
||||||
Ukrainian: "uk",
|
|
||||||
Romanian: "ro",
|
|
||||||
Czech: "cs",
|
|
||||||
Hungarian: "hu",
|
|
||||||
Bulgarian: "bg",
|
|
||||||
Croatian: "hr",
|
|
||||||
Serbian: "sr",
|
|
||||||
Slovak: "sk",
|
|
||||||
Slovenian: "sl",
|
|
||||||
Estonian: "et",
|
|
||||||
Latvian: "lv",
|
|
||||||
Lithuanian: "lt",
|
|
||||||
Icelandic: "is",
|
|
||||||
Maltese: "mt",
|
|
||||||
Georgian: "ka",
|
|
||||||
Armenian: "hy",
|
|
||||||
Azerbaijani: "az",
|
|
||||||
Kazakh: "kk",
|
|
||||||
Kyrgyz: "ky",
|
|
||||||
Uzbek: "uz",
|
|
||||||
Tajik: "tg",
|
|
||||||
Turkmen: "tk",
|
|
||||||
Mongolian: "mn",
|
|
||||||
Persian: "fa",
|
|
||||||
Urdu: "ur",
|
|
||||||
Bengali: "bn",
|
|
||||||
Tamil: "ta",
|
|
||||||
Telugu: "te",
|
|
||||||
Marathi: "mr",
|
|
||||||
Gujarati: "gu",
|
|
||||||
Kannada: "kn",
|
|
||||||
Malayalam: "ml",
|
|
||||||
Punjabi: "pa",
|
|
||||||
Sinhala: "si",
|
|
||||||
Nepali: "ne",
|
|
||||||
Burmese: "my",
|
|
||||||
Khmer: "km",
|
|
||||||
Lao: "lo",
|
|
||||||
Tibetan: "bo",
|
|
||||||
Uyghur: "ug",
|
|
||||||
Kurdish: "ku",
|
|
||||||
Pashto: "ps",
|
|
||||||
Dari: "prs",
|
|
||||||
Sindhi: "sd",
|
|
||||||
Kashmiri: "ks",
|
|
||||||
Dogri: "doi",
|
|
||||||
Konkani: "kok",
|
|
||||||
Manipuri: "mni",
|
|
||||||
Bodo: "brx",
|
|
||||||
Sanskrit: "sa",
|
|
||||||
Santhali: "sat",
|
|
||||||
Maithili: "mai",
|
|
||||||
Bhojpuri: "bho",
|
|
||||||
Awadhi: "awa",
|
|
||||||
Chhattisgarhi: "hne",
|
|
||||||
Magahi: "mag",
|
|
||||||
Rajasthani: "raj",
|
|
||||||
Malvi: "mup",
|
|
||||||
Bundeli: "bns",
|
|
||||||
Bagheli: "bfy",
|
|
||||||
Pahari: "phr",
|
|
||||||
Kumaoni: "kfy",
|
|
||||||
Garhwali: "gbm",
|
|
||||||
Kangri: "xnr",
|
|
||||||
};
|
|
||||||
|
|
||||||
return languageMap[languageName] || languageName.toLowerCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function scrapeWyzieCaptions(
|
|
||||||
tmdbId: string | number,
|
|
||||||
imdbId: string,
|
|
||||||
season?: number,
|
|
||||||
episode?: number,
|
|
||||||
): Promise<CaptionListItem[]> {
|
|
||||||
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);
|
|
||||||
|
|
||||||
const wyzieCaptions: CaptionListItem[] = wyzieSubtitles.map((subtitle) => ({
|
|
||||||
id: subtitle.id,
|
|
||||||
language: subtitle.language,
|
|
||||||
url: subtitle.url,
|
|
||||||
type:
|
|
||||||
subtitle.format === "srt" || subtitle.format === "vtt"
|
|
||||||
? subtitle.format
|
|
||||||
: "srt",
|
|
||||||
needsProxy: false,
|
|
||||||
opensubtitles: true,
|
|
||||||
// Additional metadata from Wyzie
|
|
||||||
display: subtitle.display,
|
|
||||||
media: subtitle.media,
|
|
||||||
isHearingImpaired: subtitle.isHearingImpaired,
|
|
||||||
source: `wyzie ${subtitle.source.toString() === "opensubtitles" ? "opensubs" : subtitle.source}`,
|
|
||||||
encoding: subtitle.encoding,
|
|
||||||
}));
|
|
||||||
|
|
||||||
return wyzieCaptions;
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching Wyzie subtitles:", error);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function scrapeOpenSubtitlesCaptions(
|
|
||||||
imdbId: string,
|
|
||||||
season?: number,
|
|
||||||
episode?: number,
|
|
||||||
): Promise<CaptionListItem[]> {
|
|
||||||
try {
|
|
||||||
const url = `https://rest.opensubtitles.org/search/${
|
|
||||||
season && episode ? `episode-${episode}/` : ""
|
|
||||||
}imdbid-${imdbId.slice(2)}${season && episode ? `/season-${season}` : ""}`;
|
|
||||||
|
|
||||||
const response = await fetch(url, {
|
|
||||||
headers: {
|
|
||||||
"X-User-Agent": "VLSub 0.10.2",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`OpenSubtitles API returned ${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
const openSubtitlesCaptions: CaptionListItem[] = [];
|
|
||||||
|
|
||||||
for (const caption of data) {
|
|
||||||
const downloadUrl = caption.SubDownloadLink.replace(".gz", "").replace(
|
|
||||||
"download/",
|
|
||||||
"download/subencoding-utf8/",
|
|
||||||
);
|
|
||||||
const language = labelToLanguageCode(caption.LanguageName);
|
|
||||||
|
|
||||||
if (!downloadUrl || !language) continue;
|
|
||||||
|
|
||||||
openSubtitlesCaptions.push({
|
|
||||||
id: downloadUrl,
|
|
||||||
language,
|
|
||||||
url: downloadUrl,
|
|
||||||
type: caption.SubFormat || "srt",
|
|
||||||
needsProxy: false,
|
|
||||||
opensubtitles: true,
|
|
||||||
source: "opensubs", // shortened becuase used on CaptionView for badge
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return openSubtitlesCaptions;
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching OpenSubtitles:", error);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function scrapeFebboxCaptions(
|
|
||||||
imdbId: string,
|
|
||||||
season?: number,
|
|
||||||
episode?: number,
|
|
||||||
): Promise<CaptionListItem[]> {
|
|
||||||
try {
|
|
||||||
let url: string;
|
|
||||||
if (season && episode) {
|
|
||||||
url = `https://fed-subs.pstream.mov/tv/${imdbId}/${season}/${episode}`;
|
|
||||||
} else {
|
|
||||||
url = `https://fed-subs.pstream.mov/movie/${imdbId}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// console.log("Searching Febbox subtitles with URL:", url);
|
|
||||||
|
|
||||||
const response = await fetch(url);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`Febbox API returned ${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
// Check for error response
|
|
||||||
if (data.error) {
|
|
||||||
console.log("Febbox API error:", data.error);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if subtitles exist
|
|
||||||
if (!data.subtitles || typeof data.subtitles !== "object") {
|
|
||||||
console.log("No subtitles found in Febbox response");
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const febboxCaptions: CaptionListItem[] = [];
|
|
||||||
|
|
||||||
// Iterate through all available languages
|
|
||||||
for (const [languageName, subtitleData] of Object.entries(data.subtitles)) {
|
|
||||||
if (typeof subtitleData === "object" && subtitleData !== null) {
|
|
||||||
const subtitle = subtitleData as {
|
|
||||||
subtitle_link: string;
|
|
||||||
subtitle_name: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (subtitle.subtitle_link) {
|
|
||||||
const language = labelToLanguageCode(languageName);
|
|
||||||
const fileExtension = subtitle.subtitle_link
|
|
||||||
.split(".")
|
|
||||||
.pop()
|
|
||||||
?.toLowerCase();
|
|
||||||
|
|
||||||
// Determine subtitle type based on file extension
|
|
||||||
let type: string = "srt";
|
|
||||||
if (fileExtension === "vtt") {
|
|
||||||
type = "vtt";
|
|
||||||
} else if (fileExtension === "sub") {
|
|
||||||
type = "sub";
|
|
||||||
}
|
|
||||||
|
|
||||||
febboxCaptions.push({
|
|
||||||
id: subtitle.subtitle_link,
|
|
||||||
language,
|
|
||||||
url: subtitle.subtitle_link,
|
|
||||||
type,
|
|
||||||
needsProxy: false,
|
|
||||||
opensubtitles: true,
|
|
||||||
display: subtitle.subtitle_name,
|
|
||||||
source: "febbox",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Found ${febboxCaptions.length} Febbox subtitles`);
|
|
||||||
return febboxCaptions;
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching Febbox subtitles:", error);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function scrapeVdrkCaptions(
|
|
||||||
tmdbId: string | number,
|
|
||||||
season?: number,
|
|
||||||
episode?: number,
|
|
||||||
): Promise<CaptionListItem[]> {
|
|
||||||
try {
|
|
||||||
const tmdbIdNum =
|
|
||||||
typeof tmdbId === "string" ? parseInt(tmdbId, 10) : tmdbId;
|
|
||||||
|
|
||||||
let url: string;
|
|
||||||
if (season && episode) {
|
|
||||||
// For TV shows: https://sub.vdrk.site/v1/tv/{tmdb_id}/{season}/{episode}
|
|
||||||
url = `https://sub.vdrk.site/v1/tv/${tmdbIdNum}/${season}/${episode}`;
|
|
||||||
} else {
|
|
||||||
// For movies: https://sub.vdrk.site/v1/movie/{tmdb_id}
|
|
||||||
url = `https://sub.vdrk.site/v1/movie/${tmdbIdNum}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("Searching VDRK subtitles with URL:", url);
|
|
||||||
|
|
||||||
const response = await fetch(url);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`VDRK API returned ${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
// Check if response is an array
|
|
||||||
if (!Array.isArray(data)) {
|
|
||||||
console.log("Invalid VDRK response format");
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const vdrkCaptions: CaptionListItem[] = [];
|
|
||||||
|
|
||||||
for (const subtitle of data) {
|
|
||||||
if (subtitle.file && subtitle.label) {
|
|
||||||
// Parse label to extract language and hearing impaired info
|
|
||||||
const label = subtitle.label;
|
|
||||||
const isHearingImpaired = label.includes(" Hi") || label.includes("Hi");
|
|
||||||
const languageName = label
|
|
||||||
.replace(/\s*Hi\d*$/, "")
|
|
||||||
.replace(/\s*Hi$/, "")
|
|
||||||
.replace(/\d+$/, "");
|
|
||||||
const language = labelToLanguageCode(languageName);
|
|
||||||
|
|
||||||
if (!language) continue;
|
|
||||||
|
|
||||||
vdrkCaptions.push({
|
|
||||||
id: subtitle.file,
|
|
||||||
language,
|
|
||||||
url: subtitle.file,
|
|
||||||
type: "vtt", // VDRK provides VTT files
|
|
||||||
needsProxy: false,
|
|
||||||
opensubtitles: true,
|
|
||||||
display: subtitle.label,
|
|
||||||
isHearingImpaired,
|
|
||||||
source: "granite",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Found ${vdrkCaptions.length} VDRK subtitles`);
|
|
||||||
return vdrkCaptions;
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error fetching VDRK subtitles:", error);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function scrapeExternalSubtitles(
|
|
||||||
meta: PlayerMeta,
|
|
||||||
): Promise<CaptionListItem[]> {
|
|
||||||
try {
|
|
||||||
// Extract IMDb ID from meta
|
|
||||||
const imdbId = meta.imdbId;
|
|
||||||
if (!imdbId) {
|
|
||||||
console.log("No IMDb ID available for external subtitle scraping");
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const season = meta.season?.number;
|
|
||||||
const episode = meta.episode?.number;
|
|
||||||
const tmdbId = meta.tmdbId;
|
|
||||||
|
|
||||||
// Set a reasonable timeout for each source (10 seconds)
|
|
||||||
const timeout = 10000;
|
|
||||||
|
|
||||||
// Create promises for each source with individual timeouts
|
|
||||||
const wyziePromise = scrapeWyzieCaptions(tmdbId, imdbId, season, episode);
|
|
||||||
const openSubsPromise = scrapeOpenSubtitlesCaptions(
|
|
||||||
imdbId,
|
|
||||||
season,
|
|
||||||
episode,
|
|
||||||
);
|
|
||||||
// const febboxPromise = scrapeFebboxCaptions(imdbId, season, episode);
|
|
||||||
const vdrkPromise = scrapeVdrkCaptions(tmdbId, season, episode);
|
|
||||||
|
|
||||||
// Create timeout promises
|
|
||||||
const timeoutPromise = new Promise<CaptionListItem[]>((resolve) => {
|
|
||||||
setTimeout(() => resolve([]), timeout);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Start all promises and collect results as they complete
|
|
||||||
const allCaptions: CaptionListItem[] = [];
|
|
||||||
let completedSources = 0;
|
|
||||||
const totalSources = 3;
|
|
||||||
|
|
||||||
// Helper function to handle individual source completion
|
|
||||||
const handleSourceCompletion = (
|
|
||||||
sourceName: string,
|
|
||||||
captions: CaptionListItem[],
|
|
||||||
) => {
|
|
||||||
allCaptions.push(...captions);
|
|
||||||
completedSources += 1;
|
|
||||||
console.log(
|
|
||||||
`${sourceName} completed with ${captions.length} captions (${completedSources}/${totalSources} sources done)`,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Start all sources concurrently and handle them as they complete
|
|
||||||
const promises = [
|
|
||||||
Promise.race([wyziePromise, timeoutPromise]).then((captions) => {
|
|
||||||
handleSourceCompletion("Wyzie", captions);
|
|
||||||
return captions;
|
|
||||||
}),
|
|
||||||
Promise.race([openSubsPromise, timeoutPromise]).then((captions) => {
|
|
||||||
handleSourceCompletion("OpenSubtitles", captions);
|
|
||||||
return captions;
|
|
||||||
}),
|
|
||||||
// Promise.race([febboxPromise, timeoutPromise]).then((captions) => {
|
|
||||||
// handleSourceCompletion("Febbox", captions);
|
|
||||||
// return captions;
|
|
||||||
// }),
|
|
||||||
Promise.race([vdrkPromise, timeoutPromise]).then((captions) => {
|
|
||||||
handleSourceCompletion("Granite", captions);
|
|
||||||
return captions;
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
|
|
||||||
// Wait for all sources to complete (with timeouts)
|
|
||||||
await Promise.allSettled(promises);
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
`Found ${allCaptions.length} total external captions from all sources`,
|
|
||||||
);
|
|
||||||
|
|
||||||
return allCaptions;
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error in scrapeExternalSubtitles:", error);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
182
src/utils/externalSubtitles/febbox.ts
Normal file
182
src/utils/externalSubtitles/febbox.ts
Normal file
|
|
@ -0,0 +1,182 @@
|
||||||
|
/* eslint-disable no-console */
|
||||||
|
import { CaptionListItem } from "@/stores/player/slices/source";
|
||||||
|
|
||||||
|
// Helper function to convert language names to language codes
|
||||||
|
function labelToLanguageCode(languageName: string): string {
|
||||||
|
const languageMap: Record<string, string> = {
|
||||||
|
English: "en",
|
||||||
|
Spanish: "es",
|
||||||
|
French: "fr",
|
||||||
|
German: "de",
|
||||||
|
Italian: "it",
|
||||||
|
Portuguese: "pt",
|
||||||
|
Russian: "ru",
|
||||||
|
Japanese: "ja",
|
||||||
|
Korean: "ko",
|
||||||
|
Chinese: "zh",
|
||||||
|
Arabic: "ar",
|
||||||
|
Hindi: "hi",
|
||||||
|
Turkish: "tr",
|
||||||
|
Dutch: "nl",
|
||||||
|
Polish: "pl",
|
||||||
|
Swedish: "sv",
|
||||||
|
Norwegian: "no",
|
||||||
|
Danish: "da",
|
||||||
|
Finnish: "fi",
|
||||||
|
Greek: "el",
|
||||||
|
Hebrew: "he",
|
||||||
|
Thai: "th",
|
||||||
|
Vietnamese: "vi",
|
||||||
|
Indonesian: "id",
|
||||||
|
Malay: "ms",
|
||||||
|
Filipino: "tl",
|
||||||
|
Ukrainian: "uk",
|
||||||
|
Romanian: "ro",
|
||||||
|
Czech: "cs",
|
||||||
|
Hungarian: "hu",
|
||||||
|
Bulgarian: "bg",
|
||||||
|
Croatian: "hr",
|
||||||
|
Serbian: "sr",
|
||||||
|
Slovak: "sk",
|
||||||
|
Slovenian: "sl",
|
||||||
|
Estonian: "et",
|
||||||
|
Latvian: "lv",
|
||||||
|
Lithuanian: "lt",
|
||||||
|
Icelandic: "is",
|
||||||
|
Maltese: "mt",
|
||||||
|
Georgian: "ka",
|
||||||
|
Armenian: "hy",
|
||||||
|
Azerbaijani: "az",
|
||||||
|
Kazakh: "kk",
|
||||||
|
Kyrgyz: "ky",
|
||||||
|
Uzbek: "uz",
|
||||||
|
Tajik: "tg",
|
||||||
|
Turkmen: "tk",
|
||||||
|
Mongolian: "mn",
|
||||||
|
Persian: "fa",
|
||||||
|
Urdu: "ur",
|
||||||
|
Bengali: "bn",
|
||||||
|
Tamil: "ta",
|
||||||
|
Telugu: "te",
|
||||||
|
Marathi: "mr",
|
||||||
|
Gujarati: "gu",
|
||||||
|
Kannada: "kn",
|
||||||
|
Malayalam: "ml",
|
||||||
|
Punjabi: "pa",
|
||||||
|
Sinhala: "si",
|
||||||
|
Nepali: "ne",
|
||||||
|
Burmese: "my",
|
||||||
|
Khmer: "km",
|
||||||
|
Lao: "lo",
|
||||||
|
Tibetan: "bo",
|
||||||
|
Uyghur: "ug",
|
||||||
|
Kurdish: "ku",
|
||||||
|
Pashto: "ps",
|
||||||
|
Dari: "prs",
|
||||||
|
Sindhi: "sd",
|
||||||
|
Kashmiri: "ks",
|
||||||
|
Dogri: "doi",
|
||||||
|
Konkani: "kok",
|
||||||
|
Manipuri: "mni",
|
||||||
|
Bodo: "brx",
|
||||||
|
Sanskrit: "sa",
|
||||||
|
Santhali: "sat",
|
||||||
|
Maithili: "mai",
|
||||||
|
Bhojpuri: "bho",
|
||||||
|
Awadhi: "awa",
|
||||||
|
Chhattisgarhi: "hne",
|
||||||
|
Magahi: "mag",
|
||||||
|
Rajasthani: "raj",
|
||||||
|
Malvi: "mup",
|
||||||
|
Bundeli: "bns",
|
||||||
|
Bagheli: "bfy",
|
||||||
|
Pahari: "phr",
|
||||||
|
Kumaoni: "kfy",
|
||||||
|
Garhwali: "gbm",
|
||||||
|
Kangri: "xnr",
|
||||||
|
};
|
||||||
|
|
||||||
|
return languageMap[languageName] || languageName.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function scrapeFebboxCaptions(
|
||||||
|
imdbId: string,
|
||||||
|
season?: number,
|
||||||
|
episode?: number,
|
||||||
|
): Promise<CaptionListItem[]> {
|
||||||
|
try {
|
||||||
|
let url: string;
|
||||||
|
if (season && episode) {
|
||||||
|
url = `https://fed-subs.pstream.mov/tv/${imdbId}/${season}/${episode}`;
|
||||||
|
} else {
|
||||||
|
url = `https://fed-subs.pstream.mov/movie/${imdbId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log("Searching Febbox subtitles with URL:", url);
|
||||||
|
|
||||||
|
const response = await fetch(url);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Febbox API returned ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
// Check for error response
|
||||||
|
if (data.error) {
|
||||||
|
console.log("Febbox API error:", data.error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if subtitles exist
|
||||||
|
if (!data.subtitles || typeof data.subtitles !== "object") {
|
||||||
|
console.log("No subtitles found in Febbox response");
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const febboxCaptions: CaptionListItem[] = [];
|
||||||
|
|
||||||
|
// Iterate through all available languages
|
||||||
|
for (const [languageName, subtitleData] of Object.entries(data.subtitles)) {
|
||||||
|
if (typeof subtitleData === "object" && subtitleData !== null) {
|
||||||
|
const subtitle = subtitleData as {
|
||||||
|
subtitle_link: string;
|
||||||
|
subtitle_name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (subtitle.subtitle_link) {
|
||||||
|
const language = labelToLanguageCode(languageName);
|
||||||
|
const fileExtension = subtitle.subtitle_link
|
||||||
|
.split(".")
|
||||||
|
.pop()
|
||||||
|
?.toLowerCase();
|
||||||
|
|
||||||
|
// Determine subtitle type based on file extension
|
||||||
|
let type: string = "srt";
|
||||||
|
if (fileExtension === "vtt") {
|
||||||
|
type = "vtt";
|
||||||
|
} else if (fileExtension === "sub") {
|
||||||
|
type = "sub";
|
||||||
|
}
|
||||||
|
|
||||||
|
febboxCaptions.push({
|
||||||
|
id: subtitle.subtitle_link,
|
||||||
|
language,
|
||||||
|
url: subtitle.subtitle_link,
|
||||||
|
type,
|
||||||
|
needsProxy: false,
|
||||||
|
opensubtitles: true,
|
||||||
|
display: subtitle.subtitle_name,
|
||||||
|
source: "febbox",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Found ${febboxCaptions.length} Febbox subtitles`);
|
||||||
|
return febboxCaptions;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching Febbox subtitles:", error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
100
src/utils/externalSubtitles/index.ts
Normal file
100
src/utils/externalSubtitles/index.ts
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
/* eslint-disable no-console */
|
||||||
|
import { PlayerMeta } from "@/stores/player/slices/source";
|
||||||
|
|
||||||
|
import { scrapeFebboxCaptions as _scrapeFebboxCaptions } from "./febbox";
|
||||||
|
import { scrapeOpenSubtitlesCaptions } from "./opensubtitles";
|
||||||
|
import { scrapeVdrkCaptions } from "./vdrk";
|
||||||
|
import { scrapeWyzieCaptions } from "./wyzie";
|
||||||
|
|
||||||
|
export async function scrapeExternalSubtitles(
|
||||||
|
meta: PlayerMeta,
|
||||||
|
): Promise<import("@/stores/player/slices/source").CaptionListItem[]> {
|
||||||
|
try {
|
||||||
|
// Extract IMDb ID from meta
|
||||||
|
const imdbId = meta.imdbId;
|
||||||
|
if (!imdbId) {
|
||||||
|
console.log("No IMDb ID available for external subtitle scraping");
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const season = meta.season?.number;
|
||||||
|
const episode = meta.episode?.number;
|
||||||
|
const tmdbId = meta.tmdbId;
|
||||||
|
|
||||||
|
// Set a reasonable timeout for each source (10 seconds)
|
||||||
|
const timeout = 10000;
|
||||||
|
|
||||||
|
// Create promises for each source with individual timeouts
|
||||||
|
const wyziePromise = scrapeWyzieCaptions(tmdbId, imdbId, season, episode);
|
||||||
|
const openSubsPromise = scrapeOpenSubtitlesCaptions(
|
||||||
|
imdbId,
|
||||||
|
season,
|
||||||
|
episode,
|
||||||
|
);
|
||||||
|
// const febboxPromise = scrapeFebboxCaptions(imdbId, season, episode);
|
||||||
|
const vdrkPromise = scrapeVdrkCaptions(tmdbId, season, episode);
|
||||||
|
|
||||||
|
// Create timeout promises
|
||||||
|
const timeoutPromise = new Promise<
|
||||||
|
import("@/stores/player/slices/source").CaptionListItem[]
|
||||||
|
>((resolve) => {
|
||||||
|
setTimeout(() => resolve([]), timeout);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start all promises and collect results as they complete
|
||||||
|
const allCaptions: import("@/stores/player/slices/source").CaptionListItem[] =
|
||||||
|
[];
|
||||||
|
let completedSources = 0;
|
||||||
|
const totalSources = 3;
|
||||||
|
|
||||||
|
// Helper function to handle individual source completion
|
||||||
|
const handleSourceCompletion = (
|
||||||
|
sourceName: string,
|
||||||
|
captions: import("@/stores/player/slices/source").CaptionListItem[],
|
||||||
|
) => {
|
||||||
|
allCaptions.push(...captions);
|
||||||
|
completedSources += 1;
|
||||||
|
console.log(
|
||||||
|
`${sourceName} completed with ${captions.length} captions (${completedSources}/${totalSources} sources done)`,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Start all sources concurrently and handle them as they complete
|
||||||
|
const promises = [
|
||||||
|
Promise.race([wyziePromise, timeoutPromise]).then((captions) => {
|
||||||
|
handleSourceCompletion("Wyzie", captions);
|
||||||
|
return captions;
|
||||||
|
}),
|
||||||
|
Promise.race([openSubsPromise, timeoutPromise]).then((captions) => {
|
||||||
|
handleSourceCompletion("OpenSubtitles", captions);
|
||||||
|
return captions;
|
||||||
|
}),
|
||||||
|
// Promise.race([febboxPromise, timeoutPromise]).then((captions) => {
|
||||||
|
// handleSourceCompletion("Febbox", captions);
|
||||||
|
// return captions;
|
||||||
|
// }),
|
||||||
|
Promise.race([vdrkPromise, timeoutPromise]).then((captions) => {
|
||||||
|
handleSourceCompletion("Granite", captions);
|
||||||
|
return captions;
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Wait for all sources to complete (with timeouts)
|
||||||
|
await Promise.allSettled(promises);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`Found ${allCaptions.length} total external captions from all sources`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return allCaptions;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error in scrapeExternalSubtitles:", error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-export individual functions for direct access if needed
|
||||||
|
export { scrapeWyzieCaptions } from "./wyzie";
|
||||||
|
export { scrapeOpenSubtitlesCaptions } from "./opensubtitles";
|
||||||
|
export { scrapeFebboxCaptions } from "./febbox";
|
||||||
|
export { scrapeVdrkCaptions } from "./vdrk";
|
||||||
150
src/utils/externalSubtitles/opensubtitles.ts
Normal file
150
src/utils/externalSubtitles/opensubtitles.ts
Normal file
|
|
@ -0,0 +1,150 @@
|
||||||
|
/* eslint-disable no-console */
|
||||||
|
import { CaptionListItem } from "@/stores/player/slices/source";
|
||||||
|
|
||||||
|
// Helper function to convert language names to language codes
|
||||||
|
function labelToLanguageCode(languageName: string): string {
|
||||||
|
const languageMap: Record<string, string> = {
|
||||||
|
English: "en",
|
||||||
|
Spanish: "es",
|
||||||
|
French: "fr",
|
||||||
|
German: "de",
|
||||||
|
Italian: "it",
|
||||||
|
Portuguese: "pt",
|
||||||
|
Russian: "ru",
|
||||||
|
Japanese: "ja",
|
||||||
|
Korean: "ko",
|
||||||
|
Chinese: "zh",
|
||||||
|
Arabic: "ar",
|
||||||
|
Hindi: "hi",
|
||||||
|
Turkish: "tr",
|
||||||
|
Dutch: "nl",
|
||||||
|
Polish: "pl",
|
||||||
|
Swedish: "sv",
|
||||||
|
Norwegian: "no",
|
||||||
|
Danish: "da",
|
||||||
|
Finnish: "fi",
|
||||||
|
Greek: "el",
|
||||||
|
Hebrew: "he",
|
||||||
|
Thai: "th",
|
||||||
|
Vietnamese: "vi",
|
||||||
|
Indonesian: "id",
|
||||||
|
Malay: "ms",
|
||||||
|
Filipino: "tl",
|
||||||
|
Ukrainian: "uk",
|
||||||
|
Romanian: "ro",
|
||||||
|
Czech: "cs",
|
||||||
|
Hungarian: "hu",
|
||||||
|
Bulgarian: "bg",
|
||||||
|
Croatian: "hr",
|
||||||
|
Serbian: "sr",
|
||||||
|
Slovak: "sk",
|
||||||
|
Slovenian: "sl",
|
||||||
|
Estonian: "et",
|
||||||
|
Latvian: "lv",
|
||||||
|
Lithuanian: "lt",
|
||||||
|
Icelandic: "is",
|
||||||
|
Maltese: "mt",
|
||||||
|
Georgian: "ka",
|
||||||
|
Armenian: "hy",
|
||||||
|
Azerbaijani: "az",
|
||||||
|
Kazakh: "kk",
|
||||||
|
Kyrgyz: "ky",
|
||||||
|
Uzbek: "uz",
|
||||||
|
Tajik: "tg",
|
||||||
|
Turkmen: "tk",
|
||||||
|
Mongolian: "mn",
|
||||||
|
Persian: "fa",
|
||||||
|
Urdu: "ur",
|
||||||
|
Bengali: "bn",
|
||||||
|
Tamil: "ta",
|
||||||
|
Telugu: "te",
|
||||||
|
Marathi: "mr",
|
||||||
|
Gujarati: "gu",
|
||||||
|
Kannada: "kn",
|
||||||
|
Malayalam: "ml",
|
||||||
|
Punjabi: "pa",
|
||||||
|
Sinhala: "si",
|
||||||
|
Nepali: "ne",
|
||||||
|
Burmese: "my",
|
||||||
|
Khmer: "km",
|
||||||
|
Lao: "lo",
|
||||||
|
Tibetan: "bo",
|
||||||
|
Uyghur: "ug",
|
||||||
|
Kurdish: "ku",
|
||||||
|
Pashto: "ps",
|
||||||
|
Dari: "prs",
|
||||||
|
Sindhi: "sd",
|
||||||
|
Kashmiri: "ks",
|
||||||
|
Dogri: "doi",
|
||||||
|
Konkani: "kok",
|
||||||
|
Manipuri: "mni",
|
||||||
|
Bodo: "brx",
|
||||||
|
Sanskrit: "sa",
|
||||||
|
Santhali: "sat",
|
||||||
|
Maithili: "mai",
|
||||||
|
Bhojpuri: "bho",
|
||||||
|
Awadhi: "awa",
|
||||||
|
Chhattisgarhi: "hne",
|
||||||
|
Magahi: "mag",
|
||||||
|
Rajasthani: "raj",
|
||||||
|
Malvi: "mup",
|
||||||
|
Bundeli: "bns",
|
||||||
|
Bagheli: "bfy",
|
||||||
|
Pahari: "phr",
|
||||||
|
Kumaoni: "kfy",
|
||||||
|
Garhwali: "gbm",
|
||||||
|
Kangri: "xnr",
|
||||||
|
};
|
||||||
|
|
||||||
|
return languageMap[languageName] || languageName.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function scrapeOpenSubtitlesCaptions(
|
||||||
|
imdbId: string,
|
||||||
|
season?: number,
|
||||||
|
episode?: number,
|
||||||
|
): Promise<CaptionListItem[]> {
|
||||||
|
try {
|
||||||
|
const url = `https://rest.opensubtitles.org/search/${
|
||||||
|
season && episode ? `episode-${episode}/` : ""
|
||||||
|
}imdbid-${imdbId.slice(2)}${season && episode ? `/season-${season}` : ""}`;
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
|
headers: {
|
||||||
|
"X-User-Agent": "VLSub 0.10.2",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`OpenSubtitles API returned ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
const openSubtitlesCaptions: CaptionListItem[] = [];
|
||||||
|
|
||||||
|
for (const caption of data) {
|
||||||
|
const downloadUrl = caption.SubDownloadLink.replace(".gz", "").replace(
|
||||||
|
"download/",
|
||||||
|
"download/subencoding-utf8/",
|
||||||
|
);
|
||||||
|
const language = labelToLanguageCode(caption.LanguageName);
|
||||||
|
|
||||||
|
if (!downloadUrl || !language) continue;
|
||||||
|
|
||||||
|
openSubtitlesCaptions.push({
|
||||||
|
id: downloadUrl,
|
||||||
|
language,
|
||||||
|
url: downloadUrl,
|
||||||
|
type: caption.SubFormat || "srt",
|
||||||
|
needsProxy: false,
|
||||||
|
opensubtitles: true,
|
||||||
|
source: "opensubs", // shortened becuase used on CaptionView for badge
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return openSubtitlesCaptions;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching OpenSubtitles:", error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
171
src/utils/externalSubtitles/vdrk.ts
Normal file
171
src/utils/externalSubtitles/vdrk.ts
Normal file
|
|
@ -0,0 +1,171 @@
|
||||||
|
/* eslint-disable no-console */
|
||||||
|
import { CaptionListItem } from "@/stores/player/slices/source";
|
||||||
|
|
||||||
|
// Helper function to convert language names to language codes
|
||||||
|
function labelToLanguageCode(languageName: string): string {
|
||||||
|
const languageMap: Record<string, string> = {
|
||||||
|
English: "en",
|
||||||
|
Spanish: "es",
|
||||||
|
French: "fr",
|
||||||
|
German: "de",
|
||||||
|
Italian: "it",
|
||||||
|
Portuguese: "pt",
|
||||||
|
Russian: "ru",
|
||||||
|
Japanese: "ja",
|
||||||
|
Korean: "ko",
|
||||||
|
Chinese: "zh",
|
||||||
|
Arabic: "ar",
|
||||||
|
Hindi: "hi",
|
||||||
|
Turkish: "tr",
|
||||||
|
Dutch: "nl",
|
||||||
|
Polish: "pl",
|
||||||
|
Swedish: "sv",
|
||||||
|
Norwegian: "no",
|
||||||
|
Danish: "da",
|
||||||
|
Finnish: "fi",
|
||||||
|
Greek: "el",
|
||||||
|
Hebrew: "he",
|
||||||
|
Thai: "th",
|
||||||
|
Vietnamese: "vi",
|
||||||
|
Indonesian: "id",
|
||||||
|
Malay: "ms",
|
||||||
|
Filipino: "tl",
|
||||||
|
Ukrainian: "uk",
|
||||||
|
Romanian: "ro",
|
||||||
|
Czech: "cs",
|
||||||
|
Hungarian: "hu",
|
||||||
|
Bulgarian: "bg",
|
||||||
|
Croatian: "hr",
|
||||||
|
Serbian: "sr",
|
||||||
|
Slovak: "sk",
|
||||||
|
Slovenian: "sl",
|
||||||
|
Estonian: "et",
|
||||||
|
Latvian: "lv",
|
||||||
|
Lithuanian: "lt",
|
||||||
|
Icelandic: "is",
|
||||||
|
Maltese: "mt",
|
||||||
|
Georgian: "ka",
|
||||||
|
Armenian: "hy",
|
||||||
|
Azerbaijani: "az",
|
||||||
|
Kazakh: "kk",
|
||||||
|
Kyrgyz: "ky",
|
||||||
|
Uzbek: "uz",
|
||||||
|
Tajik: "tg",
|
||||||
|
Turkmen: "tk",
|
||||||
|
Mongolian: "mn",
|
||||||
|
Persian: "fa",
|
||||||
|
Urdu: "ur",
|
||||||
|
Bengali: "bn",
|
||||||
|
Tamil: "ta",
|
||||||
|
Telugu: "te",
|
||||||
|
Marathi: "mr",
|
||||||
|
Gujarati: "gu",
|
||||||
|
Kannada: "kn",
|
||||||
|
Malayalam: "ml",
|
||||||
|
Punjabi: "pa",
|
||||||
|
Sinhala: "si",
|
||||||
|
Nepali: "ne",
|
||||||
|
Burmese: "my",
|
||||||
|
Khmer: "km",
|
||||||
|
Lao: "lo",
|
||||||
|
Tibetan: "bo",
|
||||||
|
Uyghur: "ug",
|
||||||
|
Kurdish: "ku",
|
||||||
|
Pashto: "ps",
|
||||||
|
Dari: "prs",
|
||||||
|
Sindhi: "sd",
|
||||||
|
Kashmiri: "ks",
|
||||||
|
Dogri: "doi",
|
||||||
|
Konkani: "kok",
|
||||||
|
Manipuri: "mni",
|
||||||
|
Bodo: "brx",
|
||||||
|
Sanskrit: "sa",
|
||||||
|
Santhali: "sat",
|
||||||
|
Maithili: "mai",
|
||||||
|
Bhojpuri: "bho",
|
||||||
|
Awadhi: "awa",
|
||||||
|
Chhattisgarhi: "hne",
|
||||||
|
Magahi: "mag",
|
||||||
|
Rajasthani: "raj",
|
||||||
|
Malvi: "mup",
|
||||||
|
Bundeli: "bns",
|
||||||
|
Bagheli: "bfy",
|
||||||
|
Pahari: "phr",
|
||||||
|
Kumaoni: "kfy",
|
||||||
|
Garhwali: "gbm",
|
||||||
|
Kangri: "xnr",
|
||||||
|
};
|
||||||
|
|
||||||
|
return languageMap[languageName] || languageName.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function scrapeVdrkCaptions(
|
||||||
|
tmdbId: string | number,
|
||||||
|
season?: number,
|
||||||
|
episode?: number,
|
||||||
|
): Promise<CaptionListItem[]> {
|
||||||
|
try {
|
||||||
|
const tmdbIdNum =
|
||||||
|
typeof tmdbId === "string" ? parseInt(tmdbId, 10) : tmdbId;
|
||||||
|
|
||||||
|
let url: string;
|
||||||
|
if (season && episode) {
|
||||||
|
// For TV shows: https://sub.vdrk.site/v1/tv/{tmdb_id}/{season}/{episode}
|
||||||
|
url = `https://sub.vdrk.site/v1/tv/${tmdbIdNum}/${season}/${episode}`;
|
||||||
|
} else {
|
||||||
|
// For movies: https://sub.vdrk.site/v1/movie/{tmdb_id}
|
||||||
|
url = `https://sub.vdrk.site/v1/movie/${tmdbIdNum}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Searching VDRK subtitles with URL:", url);
|
||||||
|
|
||||||
|
const response = await fetch(url);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`VDRK API returned ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
// Check if response is an array
|
||||||
|
if (!Array.isArray(data)) {
|
||||||
|
console.log("Invalid VDRK response format");
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const vdrkCaptions: CaptionListItem[] = [];
|
||||||
|
|
||||||
|
for (const subtitle of data) {
|
||||||
|
if (subtitle.file && subtitle.label) {
|
||||||
|
// Parse label to extract language and hearing impaired info
|
||||||
|
const label = subtitle.label;
|
||||||
|
const isHearingImpaired = label.includes(" Hi") || label.includes("Hi");
|
||||||
|
const languageName = label
|
||||||
|
.replace(/\s*Hi\d*$/, "")
|
||||||
|
.replace(/\s*Hi$/, "")
|
||||||
|
.replace(/\d+$/, "");
|
||||||
|
const language = labelToLanguageCode(languageName);
|
||||||
|
|
||||||
|
if (!language) continue;
|
||||||
|
|
||||||
|
vdrkCaptions.push({
|
||||||
|
id: subtitle.file,
|
||||||
|
language,
|
||||||
|
url: subtitle.file,
|
||||||
|
type: "vtt", // VDRK provides VTT files
|
||||||
|
needsProxy: false,
|
||||||
|
opensubtitles: true,
|
||||||
|
display: subtitle.label,
|
||||||
|
isHearingImpaired,
|
||||||
|
source: "granite",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Found ${vdrkCaptions.length} VDRK subtitles`);
|
||||||
|
return vdrkCaptions;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching VDRK subtitles:", error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
55
src/utils/externalSubtitles/wyzie.ts
Normal file
55
src/utils/externalSubtitles/wyzie.ts
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
/* eslint-disable no-console */
|
||||||
|
import { type SubtitleData, searchSubtitles } from "wyzie-lib";
|
||||||
|
|
||||||
|
import { CaptionListItem } from "@/stores/player/slices/source";
|
||||||
|
|
||||||
|
export async function scrapeWyzieCaptions(
|
||||||
|
tmdbId: string | number,
|
||||||
|
imdbId: string,
|
||||||
|
season?: number,
|
||||||
|
episode?: number,
|
||||||
|
): Promise<CaptionListItem[]> {
|
||||||
|
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);
|
||||||
|
|
||||||
|
const wyzieCaptions: CaptionListItem[] = wyzieSubtitles.map((subtitle) => ({
|
||||||
|
id: subtitle.id,
|
||||||
|
language: subtitle.language,
|
||||||
|
url: subtitle.url,
|
||||||
|
type:
|
||||||
|
subtitle.format === "srt" || subtitle.format === "vtt"
|
||||||
|
? subtitle.format
|
||||||
|
: "srt",
|
||||||
|
needsProxy: false,
|
||||||
|
opensubtitles: true,
|
||||||
|
// Additional metadata from Wyzie
|
||||||
|
display: subtitle.display,
|
||||||
|
media: subtitle.media,
|
||||||
|
isHearingImpaired: subtitle.isHearingImpaired,
|
||||||
|
source: `wyzie ${subtitle.source.toString() === "opensubtitles" ? "opensubs" : subtitle.source}`,
|
||||||
|
encoding: subtitle.encoding,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return wyzieCaptions;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching Wyzie subtitles:", error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue