diff --git a/src/components/player/atoms/settings/CaptionsView.tsx b/src/components/player/atoms/settings/CaptionsView.tsx index d15d64cb..7a6f85fe 100644 --- a/src/components/player/atoms/settings/CaptionsView.tsx +++ b/src/components/player/atoms/settings/CaptionsView.tsx @@ -134,6 +134,7 @@ export function CaptionOption(props: { "bg-blue-500": props.subtitleSource.includes("wyzie"), "bg-orange-500": props.subtitleSource === "opensubs", "bg-purple-500": props.subtitleSource === "febbox", + "bg-green-500": props.subtitleSource === "granite", }, )} > diff --git a/src/utils/externalSubtitles.ts b/src/utils/externalSubtitles.ts index 737af61e..66f36a5a 100644 --- a/src/utils/externalSubtitles.ts +++ b/src/utils/externalSubtitles.ts @@ -284,6 +284,77 @@ export async function scrapeFebboxCaptions( } } +export async function scrapeVdrkCaptions( + tmdbId: string | number, + season?: number, + episode?: number, +): Promise { + 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 { @@ -310,6 +381,7 @@ export async function scrapeExternalSubtitles( episode, ); // const febboxPromise = scrapeFebboxCaptions(imdbId, season, episode); + const vdrkPromise = scrapeVdrkCaptions(tmdbId, season, episode); // Create timeout promises const timeoutPromise = new Promise((resolve) => { @@ -347,6 +419,10 @@ export async function scrapeExternalSubtitles( // handleSourceCompletion("Febbox", captions); // return captions; // }), + Promise.race([vdrkPromise, timeoutPromise]).then((captions) => { + handleSourceCompletion("Granite", captions); + return captions; + }), ]; // Wait for all sources to complete (with timeouts)